feat: 帧动画编辑器美化
This commit is contained in:
@@ -0,0 +1,254 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 876191b33b10eef44b133cc291ebc912
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,3 +1,642 @@
|
||||
:root {
|
||||
--fa-bg: rgb(27, 29, 32);
|
||||
--fa-canvas: rgb(30, 32, 35);
|
||||
--fa-panel: rgb(36, 39, 43);
|
||||
--fa-panel-raised: rgb(43, 46, 51);
|
||||
--fa-panel-hover: rgb(48, 52, 58);
|
||||
--fa-field: rgb(28, 31, 34);
|
||||
--fa-line: rgb(17, 19, 21);
|
||||
--fa-line-soft: rgb(62, 67, 74);
|
||||
--fa-text: rgb(215, 217, 220);
|
||||
--fa-muted: rgb(143, 150, 158);
|
||||
--fa-accent: rgb(61, 145, 212);
|
||||
--fa-accent-soft: rgb(36, 79, 112);
|
||||
--fa-success: rgb(86, 185, 123);
|
||||
--fa-warning: rgb(224, 184, 88);
|
||||
--fa-error: rgb(223, 106, 106);
|
||||
}
|
||||
|
||||
.fa-workbench {
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
background-color: var(--fa-bg);
|
||||
color: var(--fa-text);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.fa-global-toolbar-host,
|
||||
.fa-main-host,
|
||||
.fa-bottom-host {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.fa-main-host {
|
||||
flex-grow: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.fa-workbench .unity-toolbar {
|
||||
min-height: 32px;
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
background-color: var(--fa-panel-raised);
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: var(--fa-line);
|
||||
}
|
||||
|
||||
.fa-workbench .unity-toolbar-button,
|
||||
.fa-workbench .unity-button {
|
||||
min-height: 24px;
|
||||
margin-left: 2px;
|
||||
margin-right: 2px;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
border-left-width: 1px;
|
||||
border-right-width: 1px;
|
||||
border-top-width: 1px;
|
||||
border-bottom-width: 1px;
|
||||
border-left-color: var(--fa-line);
|
||||
border-right-color: var(--fa-line);
|
||||
border-top-color: var(--fa-line);
|
||||
border-bottom-color: var(--fa-line);
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
background-color: rgb(54, 58, 63);
|
||||
color: var(--fa-text);
|
||||
}
|
||||
|
||||
.fa-workbench .unity-toolbar-button:hover,
|
||||
.fa-workbench .unity-button:hover {
|
||||
background-color: rgb(70, 75, 81);
|
||||
}
|
||||
|
||||
.fa-button--primary {
|
||||
background-color: rgb(42, 105, 150);
|
||||
border-left-color: rgb(25, 74, 104);
|
||||
border-right-color: rgb(25, 74, 104);
|
||||
border-top-color: rgb(25, 74, 104);
|
||||
border-bottom-color: rgb(25, 74, 104);
|
||||
}
|
||||
|
||||
.fa-button--danger {
|
||||
background-color: rgb(87, 54, 56);
|
||||
color: rgb(244, 205, 207);
|
||||
}
|
||||
|
||||
.fa-toolbar-group {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.fa-toolbar-separator {
|
||||
width: 1px;
|
||||
height: 21px;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
background-color: var(--fa-line);
|
||||
}
|
||||
|
||||
.fa-panel {
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
min-height: 0;
|
||||
background-color: var(--fa-panel);
|
||||
}
|
||||
|
||||
.fa-panel-header {
|
||||
height: 28px;
|
||||
min-height: 28px;
|
||||
padding-left: 9px;
|
||||
padding-right: 9px;
|
||||
-unity-text-align: middle-left;
|
||||
-unity-font-style: bold;
|
||||
background-color: rgb(44, 47, 51);
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: var(--fa-line);
|
||||
}
|
||||
|
||||
.fa-resizer--vertical {
|
||||
width: 4px;
|
||||
background-color: rgb(24, 26, 29);
|
||||
}
|
||||
|
||||
.fa-resizer--horizontal {
|
||||
height: 4px;
|
||||
background-color: rgb(24, 26, 29);
|
||||
}
|
||||
|
||||
.fa-resizer--vertical:hover,
|
||||
.fa-resizer--horizontal:hover {
|
||||
background-color: var(--fa-accent);
|
||||
}
|
||||
|
||||
.fa-resource-tabs {
|
||||
min-height: 34px;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
padding-top: 3px;
|
||||
background-color: rgb(31, 34, 38);
|
||||
}
|
||||
|
||||
.fa-resource-tabs .unity-toolbar-button,
|
||||
.fa-bottom-tabs .unity-toolbar-button {
|
||||
flex-grow: 1;
|
||||
flex-basis: 0;
|
||||
min-height: 28px;
|
||||
margin-left: 1px;
|
||||
margin-right: 1px;
|
||||
border-left-width: 0;
|
||||
border-right-width: 0;
|
||||
border-top-width: 0;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: rgba(0, 0, 0, 0);
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: rgb(169, 175, 182);
|
||||
}
|
||||
|
||||
.fa-resource-tabs .unity-toolbar-button:hover,
|
||||
.fa-bottom-tabs .unity-toolbar-button:hover {
|
||||
color: var(--fa-text);
|
||||
background-color: rgb(48, 52, 57);
|
||||
}
|
||||
|
||||
.fa-resource-tabs .unity-toolbar-button.fa-tab--active,
|
||||
.fa-bottom-tabs .unity-toolbar-button.fa-tab--active {
|
||||
color: white;
|
||||
background-color: var(--fa-panel-raised);
|
||||
border-bottom-color: var(--fa-accent);
|
||||
-unity-font-style: bold;
|
||||
}
|
||||
|
||||
.fa-resource-tools {
|
||||
padding-left: 7px;
|
||||
padding-right: 7px;
|
||||
padding-top: 7px;
|
||||
padding-bottom: 7px;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: var(--fa-line);
|
||||
}
|
||||
|
||||
.fa-resource-list {
|
||||
flex-grow: 1;
|
||||
min-height: 0;
|
||||
background-color: var(--fa-panel);
|
||||
}
|
||||
|
||||
.fa-resource-row {
|
||||
height: 48px;
|
||||
padding-left: 9px;
|
||||
padding-right: 7px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 4px;
|
||||
border-left-width: 3px;
|
||||
border-left-color: rgba(0, 0, 0, 0);
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: rgb(32, 35, 39);
|
||||
}
|
||||
|
||||
.fa-resource-row:hover {
|
||||
background-color: var(--fa-panel-hover);
|
||||
}
|
||||
|
||||
.fa-resource-row--selected {
|
||||
background-color: rgb(37, 75, 103);
|
||||
border-left-color: rgb(98, 178, 234);
|
||||
}
|
||||
|
||||
.fa-resource-row--error {
|
||||
border-left-color: var(--fa-error);
|
||||
}
|
||||
|
||||
.fa-resource-row--warning {
|
||||
border-left-color: var(--fa-warning);
|
||||
}
|
||||
|
||||
.fa-resource-row__main,
|
||||
.fa-resource-row__meta,
|
||||
.fa-badge-row {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.fa-resource-row__main {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.fa-resource-row__name {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
-unity-font-style: bold;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.fa-resource-row__id {
|
||||
max-width: 115px;
|
||||
margin-left: 5px;
|
||||
color: rgb(156, 163, 171);
|
||||
font-size: 10px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.fa-resource-row__meta {
|
||||
height: 17px;
|
||||
color: var(--fa-muted);
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.fa-resource-group {
|
||||
height: 27px;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
-unity-text-align: middle-left;
|
||||
-unity-font-style: bold;
|
||||
color: rgb(184, 189, 195);
|
||||
background-color: rgb(41, 44, 49);
|
||||
border-top-width: 1px;
|
||||
border-top-color: rgb(52, 56, 61);
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: rgb(26, 28, 31);
|
||||
}
|
||||
|
||||
.fa-badge {
|
||||
min-height: 16px;
|
||||
margin-right: 4px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
-unity-text-align: middle-center;
|
||||
font-size: 9px;
|
||||
border-top-left-radius: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
border-bottom-left-radius: 8px;
|
||||
border-bottom-right-radius: 8px;
|
||||
background-color: rgb(52, 56, 61);
|
||||
color: rgb(174, 180, 187);
|
||||
}
|
||||
|
||||
.fa-badge--blue { background-color: rgb(37, 63, 85); color: rgb(143, 202, 239); }
|
||||
.fa-badge--green { background-color: rgb(35, 63, 49); color: rgb(131, 209, 160); }
|
||||
.fa-badge--yellow { background-color: rgb(74, 62, 37); color: rgb(240, 200, 111); }
|
||||
.fa-badge--red { background-color: rgb(75, 41, 44); color: rgb(239, 141, 147); }
|
||||
.fa-badge--purple { background-color: rgb(59, 48, 75); color: rgb(195, 168, 232); }
|
||||
|
||||
.fa-flow-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
margin-right: 6px;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
.fa-canvas-panel {
|
||||
flex-grow: 1;
|
||||
min-width: 280px;
|
||||
min-height: 0;
|
||||
flex-direction: column;
|
||||
background-color: var(--fa-canvas);
|
||||
}
|
||||
|
||||
.fa-canvas-toolbar {
|
||||
min-height: 32px;
|
||||
}
|
||||
|
||||
.fa-toolbar-label {
|
||||
margin-left: 2px;
|
||||
margin-right: 4px;
|
||||
color: var(--fa-muted);
|
||||
-unity-text-align: middle-left;
|
||||
}
|
||||
|
||||
.fa-canvas-toolbar #flow-focus-field {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
.fa-flow-focus {
|
||||
min-width: 110px;
|
||||
max-width: 240px;
|
||||
padding-left: 7px;
|
||||
padding-right: 7px;
|
||||
-unity-font-style: bold;
|
||||
-unity-text-align: middle-left;
|
||||
border-left-width: 3px;
|
||||
border-left-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.fa-empty-state {
|
||||
color: var(--fa-muted);
|
||||
}
|
||||
|
||||
.fa-node {
|
||||
min-width: 224px;
|
||||
width: 224px;
|
||||
max-width: 224px;
|
||||
min-height: 164px;
|
||||
border-left-width: 1px;
|
||||
border-right-width: 1px;
|
||||
border-top-width: 1px;
|
||||
border-bottom-width: 1px;
|
||||
border-left-color: var(--fa-line);
|
||||
border-right-color: var(--fa-line);
|
||||
border-top-color: var(--fa-line);
|
||||
border-bottom-color: var(--fa-line);
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
background-color: rgb(43, 47, 52);
|
||||
}
|
||||
|
||||
.fa-node .title {
|
||||
min-height: 31px;
|
||||
padding-left: 7px;
|
||||
padding-right: 7px;
|
||||
background-color: rgb(46, 111, 155);
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: var(--fa-line);
|
||||
}
|
||||
|
||||
.fa-node .title-label {
|
||||
-unity-font-style: bold;
|
||||
}
|
||||
|
||||
.fa-node .input,
|
||||
.fa-node .output {
|
||||
min-width: 12px;
|
||||
}
|
||||
|
||||
.fa-node__meta {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
margin-left: 8px;
|
||||
margin-right: 8px;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 4px;
|
||||
color: rgb(175, 181, 188);
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.fa-node__clip-id {
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.fa-node__behavior {
|
||||
margin-left: 5px;
|
||||
color: rgb(201, 205, 210);
|
||||
}
|
||||
|
||||
.fa-node__badges {
|
||||
min-height: 21px;
|
||||
margin-left: 8px;
|
||||
margin-right: 8px;
|
||||
margin-top: 5px;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.fa-node__entry {
|
||||
min-width: 16px;
|
||||
height: 16px;
|
||||
margin-right: 5px;
|
||||
padding-left: 3px;
|
||||
padding-right: 3px;
|
||||
-unity-text-align: middle-center;
|
||||
-unity-font-style: bold;
|
||||
font-size: 9px;
|
||||
border-top-left-radius: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
border-bottom-left-radius: 8px;
|
||||
border-bottom-right-radius: 8px;
|
||||
}
|
||||
|
||||
.fa-node__preview {
|
||||
height: 78px;
|
||||
margin-left: 8px;
|
||||
margin-right: 8px;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.fa-node--focused {
|
||||
border-left-width: 2px;
|
||||
}
|
||||
|
||||
.fa-node.selected {
|
||||
border-left-width: 2px;
|
||||
border-right-width: 2px;
|
||||
border-top-width: 2px;
|
||||
border-bottom-width: 2px;
|
||||
border-left-color: rgb(105, 185, 237);
|
||||
border-right-color: rgb(105, 185, 237);
|
||||
border-top-color: rgb(105, 185, 237);
|
||||
border-bottom-color: rgb(105, 185, 237);
|
||||
}
|
||||
|
||||
.fa-node--preview-current {
|
||||
border-left-color: rgb(55, 174, 240);
|
||||
border-right-color: rgb(55, 174, 240);
|
||||
border-top-color: rgb(55, 174, 240);
|
||||
border-bottom-color: rgb(55, 174, 240);
|
||||
}
|
||||
|
||||
.fa-node--preview-passed { border-left-color: rgb(83, 154, 113); }
|
||||
.fa-node--preview-upcoming { opacity: 0.72; }
|
||||
|
||||
.fa-inspector-scroll,
|
||||
.fa-bottom-scroll {
|
||||
flex-grow: 1;
|
||||
min-height: 0;
|
||||
background-color: var(--fa-panel);
|
||||
}
|
||||
|
||||
.fa-inspector-title {
|
||||
padding-left: 11px;
|
||||
padding-right: 11px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: var(--fa-line);
|
||||
}
|
||||
|
||||
.fa-inspector-title__name {
|
||||
font-size: 15px;
|
||||
-unity-font-style: bold;
|
||||
}
|
||||
|
||||
.fa-inspector-title__id {
|
||||
margin-top: 3px;
|
||||
color: var(--fa-muted);
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.fa-section {
|
||||
padding-bottom: 8px;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: var(--fa-line);
|
||||
}
|
||||
|
||||
.fa-section__title {
|
||||
height: 27px;
|
||||
padding-left: 10px;
|
||||
-unity-text-align: middle-left;
|
||||
-unity-font-style: bold;
|
||||
background-color: rgb(41, 44, 48);
|
||||
}
|
||||
|
||||
.fa-section__body {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
padding-top: 7px;
|
||||
}
|
||||
|
||||
.fa-section__body > .unity-base-field,
|
||||
.fa-section__body > .unity-button,
|
||||
.fa-section__body > .fa-readonly-row,
|
||||
.fa-section__body > .fa-callout {
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.fa-readonly-row {
|
||||
min-height: 22px;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.fa-readonly-row__label {
|
||||
width: 118px;
|
||||
color: rgb(173, 179, 186);
|
||||
}
|
||||
|
||||
.fa-readonly-row__value {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
color: rgb(197, 202, 208);
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.fa-callout {
|
||||
padding-left: 9px;
|
||||
padding-right: 9px;
|
||||
padding-top: 7px;
|
||||
padding-bottom: 7px;
|
||||
white-space: normal;
|
||||
border-left-width: 3px;
|
||||
border-left-color: var(--fa-accent);
|
||||
background-color: rgb(34, 48, 58);
|
||||
color: rgb(188, 209, 223);
|
||||
}
|
||||
|
||||
.fa-callout--warning { border-left-color: var(--fa-warning); background-color: rgb(57, 51, 33); color: rgb(228, 208, 161); }
|
||||
.fa-callout--error { border-left-color: var(--fa-error); background-color: rgb(63, 39, 42); color: rgb(239, 184, 188); }
|
||||
|
||||
.fa-frame-row {
|
||||
min-height: 72px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: rgb(48, 51, 55);
|
||||
}
|
||||
|
||||
.fa-frame-row__readonly {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.fa-frame-row__readonly > .unity-label {
|
||||
flex-grow: 1;
|
||||
color: var(--fa-muted);
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.fa-bottom-panel {
|
||||
flex-direction: column;
|
||||
min-height: 28px;
|
||||
background-color: rgb(32, 35, 39);
|
||||
border-top-width: 1px;
|
||||
border-top-color: var(--fa-line);
|
||||
}
|
||||
|
||||
.fa-bottom-toolbar {
|
||||
min-height: 31px;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.fa-bottom-tabs {
|
||||
min-width: 230px;
|
||||
height: 30px;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.fa-bottom-tabs .unity-toolbar-button {
|
||||
min-width: 108px;
|
||||
padding-left: 14px;
|
||||
padding-right: 14px;
|
||||
}
|
||||
|
||||
.fa-bottom-toolbar .fa-bottom-collapse {
|
||||
width: 28px;
|
||||
min-width: 28px;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.fa-result-controls {
|
||||
min-height: 31px;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: var(--fa-line);
|
||||
}
|
||||
|
||||
.fa-result-header,
|
||||
.fa-result-row {
|
||||
min-height: 29px;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: rgb(41, 44, 48);
|
||||
}
|
||||
|
||||
.fa-result-header {
|
||||
min-height: 26px;
|
||||
color: var(--fa-muted);
|
||||
background-color: rgb(40, 43, 48);
|
||||
}
|
||||
|
||||
.fa-result-row:hover { background-color: var(--fa-panel-hover); }
|
||||
.fa-result-severity { width: 82px; -unity-font-style: bold; }
|
||||
.fa-result-kind { width: 135px; }
|
||||
.fa-result-object { width: 180px; }
|
||||
.fa-result-message { flex-grow: 1; min-width: 260px; white-space: normal; }
|
||||
.fa-severity--error { color: var(--fa-error); }
|
||||
.fa-severity--warning { color: var(--fa-warning); }
|
||||
.fa-severity--info { color: rgb(114, 189, 232); }
|
||||
|
||||
.fa-preview {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
@@ -25,26 +664,6 @@
|
||||
color: rgb(190, 190, 190);
|
||||
}
|
||||
|
||||
.fa-node__preview {
|
||||
height: 82px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.fa-node--preview-current {
|
||||
border-left-color: rgb(55, 174, 240);
|
||||
border-right-color: rgb(55, 174, 240);
|
||||
border-top-color: rgb(55, 174, 240);
|
||||
border-bottom-color: rgb(55, 174, 240);
|
||||
}
|
||||
|
||||
.fa-node--preview-passed {
|
||||
border-left-color: rgb(83, 154, 113);
|
||||
}
|
||||
|
||||
.fa-node--preview-upcoming {
|
||||
opacity: 0.72;
|
||||
}
|
||||
|
||||
.fa-clip-preview-panel {
|
||||
flex-shrink: 0;
|
||||
padding-left: 6px;
|
||||
@@ -55,6 +674,26 @@
|
||||
border-bottom-color: rgb(28, 28, 28);
|
||||
}
|
||||
|
||||
.fa-selection-preview-header {
|
||||
min-height: 34px;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.fa-selection-preview-title {
|
||||
color: var(--fa-text);
|
||||
-unity-font-style: bold;
|
||||
}
|
||||
|
||||
.fa-selection-preview-target {
|
||||
margin-top: 1px;
|
||||
color: var(--fa-muted);
|
||||
font-size: 10px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.fa-clip-preview-panel .fa-preview {
|
||||
height: 190px;
|
||||
margin-bottom: 4px;
|
||||
@@ -63,10 +702,48 @@
|
||||
.fa-preview-controls {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
min-height: 27px;
|
||||
}
|
||||
|
||||
.fa-selection-preview-panel .fa-preview-controls .unity-toolbar-button {
|
||||
width: 24px;
|
||||
min-width: 24px;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
margin-left: 1px;
|
||||
margin-right: 1px;
|
||||
}
|
||||
|
||||
.fa-preview-settings {
|
||||
min-height: 27px;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.fa-preview-setting {
|
||||
flex-grow: 1;
|
||||
flex-basis: 0;
|
||||
min-width: 0;
|
||||
margin-right: 5px;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.fa-preview-setting > .unity-label {
|
||||
width: 64px;
|
||||
min-width: 64px;
|
||||
color: var(--fa-muted);
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.fa-preview-setting__field {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.fa-preview-status {
|
||||
font-size: 10px;
|
||||
color: rgb(185, 185, 185);
|
||||
margin-top: 2px;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ui:UXML xmlns:ui="UnityEngine.UIElements" editor-extension-mode="True">
|
||||
<ui:VisualElement name="fa-workbench" class="fa-workbench">
|
||||
<ui:VisualElement name="global-toolbar-host" class="fa-global-toolbar-host" />
|
||||
<ui:VisualElement name="main-host" class="fa-main-host" />
|
||||
<ui:VisualElement name="bottom-host" class="fa-bottom-host" />
|
||||
</ui:VisualElement>
|
||||
</ui:UXML>
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3286d11c482fe8640a72c689bed6780c
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,10 +12,12 @@ namespace AibisDream.FrameAnimation.Editor
|
||||
{
|
||||
private readonly Label subtitle;
|
||||
private readonly Label behavior;
|
||||
private readonly Label flowBadge;
|
||||
private readonly Label issueBadge;
|
||||
private readonly Label entryBadge;
|
||||
private readonly VisualElement badgeContainer;
|
||||
private readonly FrameAnimationPreviewElement preview;
|
||||
private readonly Action<FrameAnimationClipNodeView> selectedCallback;
|
||||
private FrameAnimationFlowColorPalette? entryPalette;
|
||||
|
||||
public AnimationNode Data { get; }
|
||||
public Port InputPort { get; }
|
||||
@@ -29,30 +31,40 @@ namespace AibisDream.FrameAnimation.Editor
|
||||
this.selectedCallback = selectedCallback;
|
||||
viewDataKey = data.InternalId;
|
||||
AddToClassList("fa-node");
|
||||
style.minWidth = 245f;
|
||||
style.maxWidth = 285f;
|
||||
|
||||
InputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Input, Port.Capacity.Multi,
|
||||
typeof(AnimationNode));
|
||||
InputPort.portName = "In";
|
||||
InputPort.portName = string.Empty;
|
||||
inputContainer.Add(InputPort);
|
||||
OutputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Output, Port.Capacity.Single,
|
||||
typeof(AnimationNode));
|
||||
OutputPort.portName = "Next";
|
||||
OutputPort.portName = string.Empty;
|
||||
outputContainer.Add(OutputPort);
|
||||
|
||||
subtitle = new Label();
|
||||
subtitle.AddToClassList("fa-node__clip-id");
|
||||
behavior = new Label();
|
||||
flowBadge = new Label();
|
||||
issueBadge = new Label();
|
||||
issueBadge.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||
extensionContainer.Add(subtitle);
|
||||
extensionContainer.Add(behavior);
|
||||
extensionContainer.Add(flowBadge);
|
||||
extensionContainer.Add(issueBadge);
|
||||
behavior.AddToClassList("fa-node__behavior");
|
||||
var meta = new VisualElement();
|
||||
meta.AddToClassList("fa-node__meta");
|
||||
meta.Add(subtitle);
|
||||
meta.Add(behavior);
|
||||
extensionContainer.Add(meta);
|
||||
|
||||
preview = new FrameAnimationPreviewElement();
|
||||
preview.AddToClassList("fa-node__preview");
|
||||
extensionContainer.Add(preview);
|
||||
|
||||
badgeContainer = new VisualElement();
|
||||
badgeContainer.AddToClassList("fa-node__badges");
|
||||
entryBadge = new Label("E") { tooltip = "Flow Entry" };
|
||||
entryBadge.AddToClassList("fa-node__entry");
|
||||
badgeContainer.Add(entryBadge);
|
||||
issueBadge = new Label();
|
||||
issueBadge.AddToClassList("fa-badge");
|
||||
issueBadge.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||
badgeContainer.Add(issueBadge);
|
||||
extensionContainer.Add(badgeContainer);
|
||||
RefreshExpandedState();
|
||||
}
|
||||
|
||||
@@ -65,28 +77,67 @@ namespace AibisDream.FrameAnimation.Editor
|
||||
internal void Refresh(
|
||||
FrameAnimationGraph graph,
|
||||
IReadOnlyList<FrameAnimationEditorIssue> issues,
|
||||
IReadOnlyList<AnimationFlow> flows,
|
||||
IReadOnlyCollection<string> entryFlowIds)
|
||||
IReadOnlyList<AnimationFlow> flows)
|
||||
{
|
||||
var clip = graph.Clips.FirstOrDefault(item => item != null && item.Id == Data.ClipId);
|
||||
title = string.IsNullOrWhiteSpace(Data.DisplayName) ? Data.ClipId : Data.DisplayName;
|
||||
subtitle.text = clip != null ? $"Clip: {Data.ClipId}" : $"Clip: <Missing {Data.ClipId}>";
|
||||
subtitle.text = clip != null ? Data.ClipId : $"Missing: {Data.ClipId}";
|
||||
var speed = Data.SpeedOverride.HasValue
|
||||
? $"Speed {Data.SpeedOverride.Value:0.###} (Node)"
|
||||
: clip != null ? $"Speed {clip.Speed:0.###} (Clip)" : "Speed ?";
|
||||
var end = Data.EndBehaviorOverride?.ToString() ?? "Continue / terminal fallback";
|
||||
behavior.text = $"{speed} · {end}";
|
||||
var used = flows.Select(flow => flow.Id).ToArray();
|
||||
var entries = used.Where(entryFlowIds.Contains).ToArray();
|
||||
flowBadge.text = used.Length == 0
|
||||
? "Unused"
|
||||
: "Flows: " + string.Join(", ", used.Take(3)) + (used.Length > 3 ? $" +{used.Length - 3}" : string.Empty) +
|
||||
(entries.Length > 0 ? " [ENTRY]" : string.Empty);
|
||||
var used = flows.Where(flow => flow != null).ToArray();
|
||||
var entryFlows = used.Where(flow => flow.EntryNodeId == Data.InternalId).ToArray();
|
||||
entryPalette = entryFlows.Length > 0
|
||||
? FrameAnimationFlowColorUtility.CreatePalette(
|
||||
FrameAnimationFlowColorUtility.ResolveRaw(graph, entryFlows[0]))
|
||||
: (FrameAnimationFlowColorPalette?)null;
|
||||
entryBadge.style.display = entryPalette.HasValue ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
if (entryPalette.HasValue)
|
||||
{
|
||||
entryBadge.style.backgroundColor = entryPalette.Value.Stroke;
|
||||
entryBadge.style.color = entryPalette.Value.Text;
|
||||
entryBadge.tooltip = "Flow Entry: " + string.Join(", ", entryFlows.Select(flow => flow.Id));
|
||||
}
|
||||
|
||||
foreach (var label in badgeContainer.Query<Label>(className: "fa-flow-badge").ToList())
|
||||
{
|
||||
label.RemoveFromHierarchy();
|
||||
}
|
||||
if (used.Length == 0)
|
||||
{
|
||||
var unused = new Label("UNUSED");
|
||||
unused.AddToClassList("fa-badge");
|
||||
unused.AddToClassList("fa-badge--yellow");
|
||||
unused.AddToClassList("fa-flow-badge");
|
||||
badgeContainer.Insert(entryPalette.HasValue ? 1 : 0, unused);
|
||||
}
|
||||
else
|
||||
{
|
||||
var insertIndex = entryPalette.HasValue ? 1 : 0;
|
||||
foreach (var flow in used.Take(3))
|
||||
{
|
||||
var palette = FrameAnimationFlowColorUtility.CreatePalette(
|
||||
FrameAnimationFlowColorUtility.ResolveRaw(graph, flow));
|
||||
var badge = new Label(flow.Id) { tooltip = FlowTooltip(flow) };
|
||||
badge.AddToClassList("fa-badge");
|
||||
badge.AddToClassList("fa-flow-badge");
|
||||
badge.style.backgroundColor = palette.Muted;
|
||||
badge.style.color = palette.Text;
|
||||
badgeContainer.Insert(insertIndex++, badge);
|
||||
}
|
||||
}
|
||||
var matching = issues.Where(issue => issue.Selection.Kind == FrameAnimationEditorSelectionKind.Node &&
|
||||
ReferenceEquals(issue.Selection.Value, Data)).ToArray();
|
||||
issueBadge.text = matching.Any(issue => issue.Severity == FrameAnimationValidationSeverity.Error)
|
||||
? "ERROR"
|
||||
: matching.Length > 0 ? "WARNING" : string.Empty;
|
||||
issueBadge.style.display = matching.Length > 0 ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
issueBadge.EnableInClassList("fa-badge--red",
|
||||
matching.Any(issue => issue.Severity == FrameAnimationValidationSeverity.Error));
|
||||
issueBadge.EnableInClassList("fa-badge--yellow",
|
||||
matching.Length > 0 && matching.All(issue => issue.Severity != FrameAnimationValidationSeverity.Error));
|
||||
issueBadge.style.color = matching.Any(issue => issue.Severity == FrameAnimationValidationSeverity.Error)
|
||||
? new StyleColor(new Color(1f, 0.35f, 0.3f))
|
||||
: new StyleColor(new Color(1f, 0.72f, 0.25f));
|
||||
@@ -96,6 +147,30 @@ namespace AibisDream.FrameAnimation.Editor
|
||||
: "创建唯一的顺序后继";
|
||||
}
|
||||
|
||||
private static string FlowTooltip(AnimationFlow flow) =>
|
||||
$"Flow: {flow.DisplayName} ({flow.Id})";
|
||||
|
||||
internal void SetFocusedFlow(FrameAnimationGraph graph, AnimationFlow flow, bool belongs)
|
||||
{
|
||||
EnableInClassList("fa-node--focused", flow != null && belongs);
|
||||
if (flow != null && belongs)
|
||||
{
|
||||
var palette = FrameAnimationFlowColorUtility.CreatePalette(
|
||||
FrameAnimationFlowColorUtility.ResolveRaw(graph, flow));
|
||||
titleContainer.style.backgroundColor = palette.Header;
|
||||
var titleLabel = titleContainer.Q<Label>(className: "title-label");
|
||||
if (titleLabel != null) titleLabel.style.color = palette.Text;
|
||||
style.borderLeftColor = palette.Stroke;
|
||||
}
|
||||
else
|
||||
{
|
||||
titleContainer.style.backgroundColor = StyleKeyword.Null;
|
||||
var titleLabel = titleContainer.Q<Label>(className: "title-label");
|
||||
if (titleLabel != null) titleLabel.style.color = StyleKeyword.Null;
|
||||
style.borderLeftColor = StyleKeyword.Null;
|
||||
}
|
||||
}
|
||||
|
||||
internal void SetDimmed(bool dimmed)
|
||||
{
|
||||
style.opacity = dimmed ? 0.45f : 1f;
|
||||
@@ -119,7 +194,10 @@ namespace AibisDream.FrameAnimation.Editor
|
||||
internal sealed class FrameAnimationEdgeView : Edge
|
||||
{
|
||||
private readonly Action<FrameAnimationEdgeView> selectedCallback;
|
||||
private Color baseColor = Color.white;
|
||||
private Color baseColor = new Color32(119, 127, 135, 255);
|
||||
private bool hasError;
|
||||
private bool hasWarning;
|
||||
private FrameAnimationFlowElementState previewState;
|
||||
public AnimationEdge Data { get; }
|
||||
|
||||
internal FrameAnimationEdgeView(AnimationEdge data, Action<FrameAnimationEdgeView> selectedCallback)
|
||||
@@ -128,6 +206,7 @@ namespace AibisDream.FrameAnimation.Editor
|
||||
this.selectedCallback = selectedCallback;
|
||||
viewDataKey = data.InternalId;
|
||||
userData = data;
|
||||
RegisterCallback<AttachToPanelEvent>(_ => ApplyVisualState());
|
||||
}
|
||||
|
||||
public override void OnSelected()
|
||||
@@ -146,12 +225,9 @@ namespace AibisDream.FrameAnimation.Editor
|
||||
var matching = (issues ?? Array.Empty<FrameAnimationEditorIssue>()).Where(issue =>
|
||||
issue.Selection.Kind == FrameAnimationEditorSelectionKind.Edge &&
|
||||
ReferenceEquals(issue.Selection.Value, Data)).ToArray();
|
||||
var color = matching.Any(issue => issue.Severity == FrameAnimationValidationSeverity.Error)
|
||||
? new Color(1f, 0.3f, 0.25f)
|
||||
: matching.Length > 0 ? new Color(1f, 0.72f, 0.2f) : Color.white;
|
||||
baseColor = color;
|
||||
edgeControl.inputColor = color;
|
||||
edgeControl.outputColor = color;
|
||||
hasError = matching.Any(issue => issue.Severity == FrameAnimationValidationSeverity.Error);
|
||||
hasWarning = !hasError && matching.Length > 0;
|
||||
ApplyVisualState();
|
||||
tooltip = matching.Length == 0
|
||||
? $"{Data.ExitName} / {Data.Condition}"
|
||||
: string.Join("\n", matching.Select(issue => $"[{issue.Code}] {issue.Message}"));
|
||||
@@ -159,25 +235,47 @@ namespace AibisDream.FrameAnimation.Editor
|
||||
|
||||
internal void SetPreviewState(FrameAnimationFlowElementState state)
|
||||
{
|
||||
if (state == FrameAnimationFlowElementState.Current)
|
||||
previewState = state;
|
||||
ApplyVisualState();
|
||||
}
|
||||
|
||||
internal void SetFocusedFlow(Color? flowColor, bool belongs)
|
||||
{
|
||||
baseColor = flowColor.HasValue && belongs
|
||||
? FrameAnimationFlowColorUtility.CreatePalette(flowColor.Value).Stroke
|
||||
: new Color32(119, 127, 135, 255);
|
||||
ApplyVisualState();
|
||||
}
|
||||
|
||||
private void ApplyVisualState()
|
||||
{
|
||||
var color = hasError
|
||||
? new Color32(223, 106, 106, 255)
|
||||
: hasWarning ? new Color32(224, 184, 88, 255) : baseColor;
|
||||
var width = 3f;
|
||||
if (!hasError && !hasWarning)
|
||||
{
|
||||
edgeControl.inputColor = new Color(0.25f, 0.75f, 1f);
|
||||
edgeControl.outputColor = new Color(0.25f, 0.75f, 1f);
|
||||
if (previewState == FrameAnimationFlowElementState.Current)
|
||||
{
|
||||
color = Color.Lerp(color, Color.white, 0.28f);
|
||||
width = 5f;
|
||||
}
|
||||
else if (previewState == FrameAnimationFlowElementState.Passed)
|
||||
{
|
||||
color = Color.Lerp(color, new Color32(86, 185, 123, 255), 0.22f);
|
||||
width = 3.5f;
|
||||
}
|
||||
else if (previewState == FrameAnimationFlowElementState.Upcoming)
|
||||
{
|
||||
color = Color.Lerp(color, new Color32(43, 47, 52, 255), 0.42f);
|
||||
width = 2f;
|
||||
}
|
||||
}
|
||||
else if (state == FrameAnimationFlowElementState.Passed)
|
||||
edgeControl.inputColor = color;
|
||||
edgeControl.outputColor = color;
|
||||
if (panel != null)
|
||||
{
|
||||
edgeControl.inputColor = new Color(0.35f, 0.68f, 0.46f);
|
||||
edgeControl.outputColor = new Color(0.35f, 0.68f, 0.46f);
|
||||
}
|
||||
else if (state == FrameAnimationFlowElementState.Upcoming)
|
||||
{
|
||||
edgeControl.inputColor = new Color(0.55f, 0.55f, 0.55f);
|
||||
edgeControl.outputColor = new Color(0.55f, 0.55f, 0.55f);
|
||||
}
|
||||
else
|
||||
{
|
||||
edgeControl.inputColor = baseColor;
|
||||
edgeControl.outputColor = baseColor;
|
||||
edgeControl.edgeWidth = Mathf.RoundToInt(width);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -285,14 +383,12 @@ namespace AibisDream.FrameAnimation.Editor
|
||||
if (graph != null)
|
||||
{
|
||||
var topology = new FrameAnimationGraphTopology(graph);
|
||||
var entries = new HashSet<string>(graph.Flows.Where(flow => flow != null)
|
||||
.Select(flow => flow.EntryNodeId));
|
||||
foreach (var node in graph.Nodes.Where(node => node != null))
|
||||
{
|
||||
var view = new FrameAnimationClipNodeView(node, OnNodeSelected);
|
||||
view.SetPosition(new Rect(FrameAnimationGraphMutationService.GetNodePosition(graph, node),
|
||||
new Vector2(255f, 150f)));
|
||||
view.Refresh(graph, issues, topology.FindFlowsUsingNode(node.InternalId), entries);
|
||||
new Vector2(224f, 164f)));
|
||||
view.Refresh(graph, issues, topology.FindFlowsUsingNode(node.InternalId));
|
||||
nodeViews[node.InternalId] = view;
|
||||
AddElement(view);
|
||||
}
|
||||
@@ -372,8 +468,16 @@ namespace AibisDream.FrameAnimation.Editor
|
||||
focusedFlowId = flowId ?? string.Empty;
|
||||
if (graph == null || string.IsNullOrEmpty(flowId))
|
||||
{
|
||||
foreach (var view in nodeViews.Values) view.SetDimmed(false);
|
||||
foreach (var view in edgeViews.Values) view.SetDimmed(false);
|
||||
foreach (var view in nodeViews.Values)
|
||||
{
|
||||
view.SetDimmed(false);
|
||||
view.SetFocusedFlow(graph, null, false);
|
||||
}
|
||||
foreach (var view in edgeViews.Values)
|
||||
{
|
||||
view.SetDimmed(false);
|
||||
view.SetFocusedFlow(null, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var flow = graph.Flows.FirstOrDefault(item => item != null && item.Id == flowId);
|
||||
@@ -383,8 +487,19 @@ namespace AibisDream.FrameAnimation.Editor
|
||||
: new FrameAnimationReachability(Array.Empty<AnimationNode>(), Array.Empty<AnimationEdge>());
|
||||
var nodeIds = new HashSet<string>(reachable.Nodes.Select(node => node.InternalId));
|
||||
var edgeIds = new HashSet<string>(reachable.Edges.Select(edge => edge.InternalId));
|
||||
foreach (var pair in nodeViews) pair.Value.SetDimmed(!nodeIds.Contains(pair.Key));
|
||||
foreach (var pair in edgeViews) pair.Value.SetDimmed(!edgeIds.Contains(pair.Key));
|
||||
var flowColor = flow != null ? FrameAnimationFlowColorUtility.ResolveRaw(graph, flow) : (Color?)null;
|
||||
foreach (var pair in nodeViews)
|
||||
{
|
||||
var belongs = nodeIds.Contains(pair.Key);
|
||||
pair.Value.SetDimmed(!belongs);
|
||||
pair.Value.SetFocusedFlow(graph, flow, belongs);
|
||||
}
|
||||
foreach (var pair in edgeViews)
|
||||
{
|
||||
var belongs = edgeIds.Contains(pair.Key);
|
||||
pair.Value.SetDimmed(!belongs);
|
||||
pair.Value.SetFocusedFlow(flowColor, belongs);
|
||||
}
|
||||
}
|
||||
|
||||
internal void SelectAndFrame(FrameAnimationEditorSelection target)
|
||||
|
||||
@@ -12,6 +12,7 @@ using UnityEditor;
|
||||
using UnityEditor.AddressableAssets;
|
||||
using UnityEditor.AddressableAssets.Settings;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace AibisDream.EditorTools
|
||||
{
|
||||
@@ -479,6 +480,7 @@ namespace AibisDream.EditorTools
|
||||
var context = new SnapshotRestoreContext(new SaveSnapshot(), strictMode: true);
|
||||
context.SetPhase("Provider restore");
|
||||
context.Warn("optional state missing");
|
||||
LogAssert.Expect(LogType.Error, "[SnapshotRestore] required state missing");
|
||||
context.Error("required state missing");
|
||||
|
||||
Assert.That(context.StrictMode, Is.True);
|
||||
|
||||
@@ -137,6 +137,22 @@ namespace AibisDream.FrameAnimation.Tests.EditMode
|
||||
Assert.That(graph.EditorData.FlowEditorData.Single().FlowId, Is.EqualTo("NewFlow"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetFlowColor_IsStoredAndSupportsUndo()
|
||||
{
|
||||
var flow = new AnimationFlow("Flow", "Flow", "entry");
|
||||
var graph = CreateGraph("FlowColor.asset", flows: new[] { flow });
|
||||
graph.EditorData.GetOrCreateFlowData(flow.Id, Color.red);
|
||||
|
||||
FrameAnimationGraphMutationService.SetFlowColor(graph, flow, new Color(0.1f, 0.4f, 0.9f, 0.2f));
|
||||
Assert.That(graph.EditorData.FlowEditorData.Single().Color.b, Is.EqualTo(0.9f).Within(0.001f));
|
||||
Assert.That(graph.EditorData.FlowEditorData.Single().Color.a, Is.EqualTo(0.2f).Within(0.001f));
|
||||
|
||||
Undo.FlushUndoRecordObjects();
|
||||
Undo.PerformUndo();
|
||||
Assert.That(graph.EditorData.FlowEditorData.Single().Color, Is.EqualTo(Color.red));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ResourceQuery_SearchFilterAndSort_AreStable()
|
||||
{
|
||||
|
||||
@@ -179,6 +179,92 @@ namespace AibisDream.FrameAnimation.Tests.EditMode
|
||||
Assert.That(JsonUtility.ToJson(graph), Is.EqualTo(before));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FlowColorPalette_IsOpaqueAndReadableForExtremeColors()
|
||||
{
|
||||
var dark = FrameAnimationFlowColorUtility.CreatePalette(new Color(0.005f, 0.01f, 0.02f, 0f));
|
||||
var light = FrameAnimationFlowColorUtility.CreatePalette(new Color(1f, 0.98f, 0.9f, 0.1f));
|
||||
|
||||
Assert.That(dark.Raw.a, Is.EqualTo(1f));
|
||||
Assert.That(dark.Stroke.maxColorComponent, Is.GreaterThan(dark.Raw.maxColorComponent));
|
||||
Assert.That(light.Raw.a, Is.EqualTo(1f));
|
||||
Assert.That(FrameAnimationFlowColorUtility.Luminance(light.Text),
|
||||
Is.LessThan(FrameAnimationFlowColorUtility.Luminance(light.Header)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GraphView_FocusedFlowUsesItsColorAndShowAllKeepsNeutralEdges()
|
||||
{
|
||||
var clip = Clip("Clip", 1f, FrameClipEndBehavior.HoldLastFrame, 100);
|
||||
var first = new AnimationNode(clip.Id, internalId: "first");
|
||||
var second = new AnimationNode(clip.Id, internalId: "second");
|
||||
var edge = new AnimationEdge(first.InternalId, second.InternalId, internalId: "edge");
|
||||
var flow = new AnimationFlow("Flow", "Flow", first.InternalId);
|
||||
var graph = Graph(new[] { clip }, new[] { first, second }, new[] { edge }, new[] { flow });
|
||||
var stored = new Color(0.12f, 0.72f, 0.38f, 0.15f);
|
||||
graph.EditorData.GetOrCreateFlowData(flow.Id, stored);
|
||||
var palette = FrameAnimationFlowColorUtility.CreatePalette(stored);
|
||||
var view = new FrameAnimationGraphView();
|
||||
|
||||
view.Bind(graph, Array.Empty<FrameAnimationEditorIssue>(), string.Empty);
|
||||
var edgeView = view.Query<FrameAnimationEdgeView>().First();
|
||||
Assert.That(edgeView.edgeControl.inputColor.r, Is.EqualTo(119f / 255f).Within(0.001f));
|
||||
Assert.That(view.Query<Label>(className: "fa-flow-badge").ToList().Count, Is.EqualTo(2));
|
||||
|
||||
view.SetFocusedFlow(flow.Id);
|
||||
|
||||
Assert.That(edgeView.edgeControl.inputColor.r, Is.EqualTo(palette.Stroke.r).Within(0.003f));
|
||||
Assert.That(edgeView.edgeControl.inputColor.g, Is.EqualTo(palette.Stroke.g).Within(0.003f));
|
||||
Assert.That(view.Query<FrameAnimationClipNodeView>(className: "fa-node--focused").ToList().Count,
|
||||
Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GraphView_SharedPathSwitchesToNewFocusedFlowColor()
|
||||
{
|
||||
var clip = Clip("Clip", 1f, FrameClipEndBehavior.HoldLastFrame, 100);
|
||||
var first = new AnimationNode(clip.Id, internalId: "first");
|
||||
var second = new AnimationNode(clip.Id, internalId: "second");
|
||||
var edge = new AnimationEdge(first.InternalId, second.InternalId, internalId: "edge");
|
||||
var redFlow = new AnimationFlow("Red", "Red", first.InternalId);
|
||||
var blueFlow = new AnimationFlow("Blue", "Blue", first.InternalId);
|
||||
var graph = Graph(new[] { clip }, new[] { first, second }, new[] { edge }, new[] { redFlow, blueFlow });
|
||||
graph.EditorData.GetOrCreateFlowData(redFlow.Id, Color.red);
|
||||
graph.EditorData.GetOrCreateFlowData(blueFlow.Id, Color.blue);
|
||||
var view = new FrameAnimationGraphView();
|
||||
view.Bind(graph, Array.Empty<FrameAnimationEditorIssue>(), redFlow.Id);
|
||||
var edgeView = view.Query<FrameAnimationEdgeView>().First();
|
||||
var red = edgeView.edgeControl.inputColor;
|
||||
|
||||
view.SetFocusedFlow(blueFlow.Id);
|
||||
var blue = edgeView.edgeControl.inputColor;
|
||||
|
||||
Assert.That(red.r, Is.GreaterThan(red.b));
|
||||
Assert.That(blue.b, Is.GreaterThan(blue.r));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GraphView_EdgeValidationColorOverridesFlowAndPreviewColor()
|
||||
{
|
||||
var clip = Clip("Clip", 1f, FrameClipEndBehavior.HoldLastFrame, 100);
|
||||
var first = new AnimationNode(clip.Id, internalId: "first");
|
||||
var second = new AnimationNode(clip.Id, internalId: "second");
|
||||
var edge = new AnimationEdge(first.InternalId, second.InternalId, internalId: "edge");
|
||||
var flow = new AnimationFlow("Flow", "Flow", first.InternalId);
|
||||
var graph = Graph(new[] { clip }, new[] { first, second }, new[] { edge }, new[] { flow });
|
||||
graph.EditorData.GetOrCreateFlowData(flow.Id, Color.green);
|
||||
var issue = new FrameAnimationEditorIssue(FrameAnimationValidationSeverity.Error, "BrokenEdge",
|
||||
new FrameAnimationEditorSelection(FrameAnimationEditorSelectionKind.Edge, edge), "Broken", string.Empty);
|
||||
var view = new FrameAnimationGraphView();
|
||||
view.Bind(graph, new[] { issue }, flow.Id);
|
||||
var edgeView = view.Query<FrameAnimationEdgeView>().First();
|
||||
|
||||
edgeView.SetPreviewState(FrameAnimationFlowElementState.Current);
|
||||
|
||||
Assert.That(edgeView.edgeControl.inputColor.r, Is.EqualTo(223f / 255f).Within(0.003f));
|
||||
Assert.That(edgeView.edgeControl.inputColor.g, Is.EqualTo(106f / 255f).Within(0.003f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PreviewSample_IsIsolatedAndStructurallyPlayable()
|
||||
{
|
||||
@@ -197,7 +283,7 @@ namespace AibisDream.FrameAnimation.Tests.EditMode
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Workbench_CreateGUIBuildsSharedAndClipPreviewElements()
|
||||
public void Workbench_CreateGUIBuildsTabsAndSelectionPreviewForClipNodeAndFlow()
|
||||
{
|
||||
var sample = AssetDatabase.LoadAssetAtPath<FrameAnimationGraph>(
|
||||
"Assets/GameContent/Test/FrameAnimation/Preview/PreviewSampleGraph.asset");
|
||||
@@ -215,14 +301,57 @@ namespace AibisDream.FrameAnimation.Tests.EditMode
|
||||
Is.GreaterThanOrEqualTo(2));
|
||||
Assert.That(window.rootVisualElement.Q<Button>("graph-properties-button"), Is.Not.Null);
|
||||
Assert.That(window.rootVisualElement.Q<Button>("graph-breadcrumb-button"), Is.Not.Null);
|
||||
Assert.That(window.rootVisualElement.Q<VisualElement>("canvas-toolbar").Query<Slider>().ToList(),
|
||||
Is.Empty);
|
||||
Assert.That(window.rootVisualElement.Query<IMGUIContainer>().ToList(), Is.Empty);
|
||||
Assert.That(window.rootVisualElement.Q<VisualElement>("fa-workbench"), Is.Not.Null);
|
||||
Assert.That(window.rootVisualElement.Q<Button>("resource-tab-clips")
|
||||
.ClassListContains("fa-tab--active"), Is.True);
|
||||
Assert.That(window.rootVisualElement.Q<Button>("bottom-tab-import-diff")
|
||||
.ClassListContains("fa-tab--active"), Is.True);
|
||||
|
||||
var flowsTab = window.rootVisualElement.Q<Button>("resource-tab-flows");
|
||||
typeof(FrameAnimationGraphEditorWindow).GetMethod("SetResourceTab",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic)?.Invoke(window,
|
||||
new[] { flowsTab.userData, (object)true });
|
||||
Assert.That(flowsTab.ClassListContains("fa-tab--active"), Is.True);
|
||||
Assert.That(window.rootVisualElement.Q<Button>("resource-tab-clips")
|
||||
.ClassListContains("fa-tab--active"), Is.False);
|
||||
|
||||
var validationTab = window.rootVisualElement.Q<Button>("bottom-tab-validation");
|
||||
typeof(FrameAnimationGraphEditorWindow).GetMethod("SetBottomTab",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic)?.Invoke(window,
|
||||
new[] { validationTab.userData });
|
||||
Assert.That(validationTab.ClassListContains("fa-tab--active"), Is.True);
|
||||
Assert.That(window.rootVisualElement.Q<Button>("bottom-tab-import-diff")
|
||||
.ClassListContains("fa-tab--active"), Is.False);
|
||||
|
||||
var clip = sample.Clips.First(item => item != null);
|
||||
typeof(FrameAnimationGraphEditorWindow).GetMethod("SetSelection",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic)?.Invoke(window, new object[]
|
||||
var setSelection = typeof(FrameAnimationGraphEditorWindow).GetMethod("SetSelection",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
setSelection?.Invoke(window, new object[]
|
||||
{
|
||||
new FrameAnimationEditorSelection(FrameAnimationEditorSelectionKind.Clip, clip),
|
||||
false
|
||||
});
|
||||
AssertSelectionPreview(window, "Clip Preview");
|
||||
|
||||
var node = sample.Nodes.First(item => item != null);
|
||||
setSelection?.Invoke(window, new object[]
|
||||
{
|
||||
new FrameAnimationEditorSelection(FrameAnimationEditorSelectionKind.Node, node),
|
||||
false
|
||||
});
|
||||
AssertSelectionPreview(window, "Node Preview");
|
||||
|
||||
var flow = sample.Flows.First(item => item != null);
|
||||
setSelection?.Invoke(window, new object[]
|
||||
{
|
||||
new FrameAnimationEditorSelection(FrameAnimationEditorSelectionKind.Flow, flow),
|
||||
false
|
||||
});
|
||||
AssertSelectionPreview(window, "Flow Preview");
|
||||
|
||||
typeof(FrameAnimationGraphEditorWindow).GetMethod("SelectGraphProperties",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic)?.Invoke(window, null);
|
||||
var selected = (FrameAnimationEditorSelection)typeof(FrameAnimationGraphEditorWindow)
|
||||
@@ -236,6 +365,15 @@ namespace AibisDream.FrameAnimation.Tests.EditMode
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssertSelectionPreview(FrameAnimationGraphEditorWindow window, string expectedTitle)
|
||||
{
|
||||
var panel = window.rootVisualElement.Q<VisualElement>("selection-preview-panel");
|
||||
Assert.That(panel, Is.Not.Null);
|
||||
Assert.That(panel.style.display.value, Is.EqualTo(DisplayStyle.Flex));
|
||||
Assert.That(window.rootVisualElement.Q<Label>("selection-preview-title").text,
|
||||
Is.EqualTo(expectedTitle));
|
||||
}
|
||||
|
||||
private FrameClip Clip(string id, float speed, FrameClipEndBehavior end, params int[] durations)
|
||||
{
|
||||
var clip = Track(ScriptableObject.CreateInstance<FrameClip>());
|
||||
|
||||
Reference in New Issue
Block a user