Files
aibis-dream/Assets/Editor/FrameAnimation/FrameAnimationEditorElements.cs

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();
}
}
}