255 lines
11 KiB
C#
255 lines
11 KiB
C#
using System;
|
|
using UnityEditor;
|
|
using UnityEditor.UIElements;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
|
|
namespace AibisDream.FrameAnimation.Editor
|
|
{
|
|
internal enum FrameAnimationBrowserEntryKind
|
|
{
|
|
Header,
|
|
Clip,
|
|
Flow,
|
|
Source
|
|
}
|
|
|
|
internal enum FrameAnimationBrowserIssueState
|
|
{
|
|
None,
|
|
Warning,
|
|
Error
|
|
}
|
|
|
|
internal sealed class FrameAnimationBrowserEntry
|
|
{
|
|
public FrameAnimationBrowserEntryKind Kind;
|
|
public string Title = string.Empty;
|
|
public string Id = string.Empty;
|
|
public string TypeLabel = string.Empty;
|
|
public string Meta = string.Empty;
|
|
public string StatusLabel = string.Empty;
|
|
public Color? AccentColor;
|
|
public FrameAnimationBrowserIssueState IssueState;
|
|
public FrameAnimationEditorSelection Selection;
|
|
|
|
public bool IsHeader => Kind == FrameAnimationBrowserEntryKind.Header;
|
|
}
|
|
|
|
internal readonly struct FrameAnimationFlowColorPalette
|
|
{
|
|
public readonly Color Raw;
|
|
public readonly Color Stroke;
|
|
public readonly Color Header;
|
|
public readonly Color Muted;
|
|
public readonly Color Text;
|
|
|
|
public FrameAnimationFlowColorPalette(Color raw, Color stroke, Color header, Color muted, Color text)
|
|
{
|
|
Raw = raw;
|
|
Stroke = stroke;
|
|
Header = header;
|
|
Muted = muted;
|
|
Text = text;
|
|
}
|
|
}
|
|
|
|
internal static class FrameAnimationFlowColorUtility
|
|
{
|
|
private static readonly Color Panel = new Color32(43, 47, 52, 255);
|
|
private static readonly Color LightText = new Color32(244, 246, 248, 255);
|
|
private static readonly Color DarkText = new Color32(24, 27, 30, 255);
|
|
private static readonly Color Neutral = new Color32(100, 168, 216, 255);
|
|
|
|
internal static Color ResolveRaw(FrameAnimationGraph graph, AnimationFlow flow)
|
|
{
|
|
return Opaque(ResolveStored(graph, flow));
|
|
}
|
|
|
|
internal static Color ResolveStored(FrameAnimationGraph graph, AnimationFlow flow)
|
|
{
|
|
if (graph == null || flow == null)
|
|
{
|
|
return Neutral;
|
|
}
|
|
var data = graph.EditorData?.FlowEditorData == null
|
|
? null
|
|
: System.Linq.Enumerable.FirstOrDefault(graph.EditorData.FlowEditorData,
|
|
item => item != null && item.FlowId == flow.Id);
|
|
return data?.Color ?? Neutral;
|
|
}
|
|
|
|
internal static FrameAnimationFlowColorPalette CreatePalette(Color value)
|
|
{
|
|
var raw = Opaque(value);
|
|
var luminance = Luminance(raw);
|
|
var stroke = luminance < 0.30f
|
|
? Color.Lerp(raw, Color.white, Mathf.InverseLerp(0.30f, 0.02f, luminance) * 0.42f)
|
|
: luminance > 0.82f
|
|
? Color.Lerp(raw, Color.black, Mathf.InverseLerp(0.82f, 1f, luminance) * 0.22f)
|
|
: raw;
|
|
stroke.a = 1f;
|
|
var header = Color.Lerp(Panel, stroke, 0.68f);
|
|
header.a = 1f;
|
|
var muted = Color.Lerp(Panel, stroke, 0.42f);
|
|
muted.a = 1f;
|
|
var text = Luminance(header) > 0.22f ? DarkText : LightText;
|
|
return new FrameAnimationFlowColorPalette(raw, stroke, header, muted, text);
|
|
}
|
|
|
|
internal static Color Opaque(Color value) => new Color(
|
|
Mathf.Clamp01(value.r), Mathf.Clamp01(value.g), Mathf.Clamp01(value.b), 1f);
|
|
|
|
internal static float Luminance(Color value)
|
|
{
|
|
var linear = value.linear;
|
|
return linear.r * 0.2126f + linear.g * 0.7152f + linear.b * 0.0722f;
|
|
}
|
|
}
|
|
|
|
internal sealed class FrameAnimationResourceRowElement : VisualElement
|
|
{
|
|
private readonly VisualElement flowDot;
|
|
private readonly Label typeBadge;
|
|
private readonly Label titleLabel;
|
|
private readonly Label idLabel;
|
|
private readonly Label metaLabel;
|
|
private readonly Label statusBadge;
|
|
|
|
internal FrameAnimationResourceRowElement()
|
|
{
|
|
AddToClassList("fa-resource-row");
|
|
|
|
var main = new VisualElement();
|
|
main.AddToClassList("fa-resource-row__main");
|
|
flowDot = new VisualElement { pickingMode = PickingMode.Ignore };
|
|
flowDot.AddToClassList("fa-flow-dot");
|
|
typeBadge = new Label { pickingMode = PickingMode.Ignore };
|
|
typeBadge.AddToClassList("fa-badge");
|
|
titleLabel = new Label { pickingMode = PickingMode.Ignore };
|
|
titleLabel.AddToClassList("fa-resource-row__name");
|
|
idLabel = new Label { pickingMode = PickingMode.Ignore };
|
|
idLabel.AddToClassList("fa-resource-row__id");
|
|
statusBadge = new Label { pickingMode = PickingMode.Ignore };
|
|
statusBadge.AddToClassList("fa-badge");
|
|
main.Add(flowDot);
|
|
main.Add(typeBadge);
|
|
main.Add(titleLabel);
|
|
main.Add(idLabel);
|
|
main.Add(statusBadge);
|
|
|
|
metaLabel = new Label { pickingMode = PickingMode.Ignore };
|
|
metaLabel.AddToClassList("fa-resource-row__meta");
|
|
Add(main);
|
|
Add(metaLabel);
|
|
}
|
|
|
|
internal void Bind(FrameAnimationBrowserEntry entry, bool selected)
|
|
{
|
|
userData = entry;
|
|
var isHeader = entry?.IsHeader == true;
|
|
EnableInClassList("fa-resource-group", isHeader);
|
|
EnableInClassList("fa-resource-row--selected", selected);
|
|
EnableInClassList("fa-resource-row--error", entry?.IssueState == FrameAnimationBrowserIssueState.Error);
|
|
EnableInClassList("fa-resource-row--warning", entry?.IssueState == FrameAnimationBrowserIssueState.Warning);
|
|
if (entry == null)
|
|
{
|
|
style.display = DisplayStyle.None;
|
|
return;
|
|
}
|
|
style.display = DisplayStyle.Flex;
|
|
titleLabel.text = entry.Title;
|
|
titleLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
|
|
idLabel.text = entry.Id;
|
|
metaLabel.text = entry.Meta;
|
|
typeBadge.text = entry.TypeLabel;
|
|
typeBadge.style.display = isHeader ? DisplayStyle.None : DisplayStyle.Flex;
|
|
idLabel.style.display = isHeader ? DisplayStyle.None : DisplayStyle.Flex;
|
|
metaLabel.style.display = isHeader ? DisplayStyle.None : DisplayStyle.Flex;
|
|
statusBadge.text = entry.StatusLabel;
|
|
statusBadge.style.display = isHeader || string.IsNullOrEmpty(entry.StatusLabel) ? DisplayStyle.None : DisplayStyle.Flex;
|
|
flowDot.style.display = !isHeader && entry.AccentColor.HasValue ? DisplayStyle.Flex : DisplayStyle.None;
|
|
if (entry.AccentColor.HasValue)
|
|
{
|
|
flowDot.style.backgroundColor = FrameAnimationFlowColorUtility.CreatePalette(entry.AccentColor.Value).Stroke;
|
|
}
|
|
typeBadge.EnableInClassList("fa-badge--blue", entry.Kind == FrameAnimationBrowserEntryKind.Clip);
|
|
typeBadge.EnableInClassList("fa-badge--purple", entry.Kind == FrameAnimationBrowserEntryKind.Flow);
|
|
typeBadge.EnableInClassList("fa-badge--green", entry.Kind == FrameAnimationBrowserEntryKind.Source);
|
|
statusBadge.EnableInClassList("fa-badge--red", entry.IssueState == FrameAnimationBrowserIssueState.Error);
|
|
statusBadge.EnableInClassList("fa-badge--yellow", entry.IssueState == FrameAnimationBrowserIssueState.Warning);
|
|
}
|
|
}
|
|
|
|
internal sealed class FrameAnimationFrameRowElement : VisualElement
|
|
{
|
|
private readonly ObjectField spriteField;
|
|
private readonly IntegerField durationField;
|
|
private readonly Label frameNameLabel;
|
|
private readonly Label sourceIndexLabel;
|
|
private FrameClip clip;
|
|
private int index;
|
|
private bool readOnly;
|
|
private Action changed;
|
|
|
|
internal FrameAnimationFrameRowElement()
|
|
{
|
|
AddToClassList("fa-frame-row");
|
|
spriteField = new ObjectField("Sprite") { objectType = typeof(Sprite), allowSceneObjects = false };
|
|
durationField = new IntegerField("Duration Ms");
|
|
var readOnlyRow = new VisualElement();
|
|
readOnlyRow.AddToClassList("fa-frame-row__readonly");
|
|
frameNameLabel = new Label();
|
|
sourceIndexLabel = new Label();
|
|
readOnlyRow.Add(frameNameLabel);
|
|
readOnlyRow.Add(sourceIndexLabel);
|
|
Add(spriteField);
|
|
Add(durationField);
|
|
Add(readOnlyRow);
|
|
spriteField.RegisterValueChangedCallback(OnSpriteChanged);
|
|
durationField.RegisterValueChangedCallback(OnDurationChanged);
|
|
}
|
|
|
|
internal void Bind(FrameClip value, int itemIndex, bool isReadOnly, Action onChanged)
|
|
{
|
|
clip = value;
|
|
index = itemIndex;
|
|
readOnly = isReadOnly;
|
|
changed = onChanged;
|
|
userData = itemIndex;
|
|
var frame = clip != null && index >= 0 && index < clip.Frames.Count ? clip.Frames[index] : null;
|
|
spriteField.SetValueWithoutNotify(frame?.Sprite);
|
|
durationField.SetValueWithoutNotify(frame?.DurationMs ?? 100);
|
|
frameNameLabel.text = "Frame " + (frame?.FrameName ?? string.Empty);
|
|
sourceIndexLabel.text = "Source " + (frame?.SourceIndex.ToString() ?? "-1");
|
|
spriteField.SetEnabled(!readOnly);
|
|
durationField.SetEnabled(!readOnly);
|
|
}
|
|
|
|
private void OnSpriteChanged(ChangeEvent<UnityEngine.Object> evt)
|
|
{
|
|
if (readOnly || clip == null || index < 0 || index >= clip.Frames.Count) return;
|
|
var serialized = new SerializedObject(clip);
|
|
serialized.Update();
|
|
var element = serialized.FindProperty("frames").GetArrayElementAtIndex(index);
|
|
element.FindPropertyRelative("sprite").objectReferenceValue = evt.newValue;
|
|
serialized.ApplyModifiedProperties();
|
|
EditorUtility.SetDirty(clip);
|
|
changed?.Invoke();
|
|
}
|
|
|
|
private void OnDurationChanged(ChangeEvent<int> evt)
|
|
{
|
|
if (readOnly || clip == null || index < 0 || index >= clip.Frames.Count) return;
|
|
var serialized = new SerializedObject(clip);
|
|
serialized.Update();
|
|
var element = serialized.FindProperty("frames").GetArrayElementAtIndex(index);
|
|
element.FindPropertyRelative("durationMs").intValue = Mathf.Max(1, evt.newValue);
|
|
serialized.ApplyModifiedProperties();
|
|
durationField.SetValueWithoutNotify(Mathf.Max(1, evt.newValue));
|
|
EditorUtility.SetDirty(clip);
|
|
changed?.Invoke();
|
|
}
|
|
}
|
|
}
|