From 982a1eedbeae34e590087b0a429c54fe3d90a4c3 Mon Sep 17 00:00:00 2001 From: Ding Yuntian <1491671119@qq.com> Date: Mon, 20 Jul 2026 22:04:32 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E5=B8=A7=E5=8A=A8=E7=94=BB?= =?UTF-8?q?=E7=BC=96=E8=BE=91=E5=99=A8=E7=BE=8E=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FrameAnimationEditorElements.cs | 254 ++++ .../FrameAnimationEditorElements.cs.meta | 11 + .../FrameAnimationGraphEditor.uss | 717 +++++++++- .../FrameAnimationGraphEditor.uxml | 8 + .../FrameAnimationGraphEditor.uxml.meta | 10 + .../FrameAnimationGraphEditorWindow.cs | 1261 ++++++++++++++--- .../FrameAnimation/FrameAnimationGraphView.cs | 219 ++- .../DevSavePromoteWindow.cs | 2 + .../FrameAnimationEditorWorkspaceTests.cs | 16 + .../EditMode/FrameAnimationPreviewTests.cs | 144 +- 10 files changed, 2369 insertions(+), 273 deletions(-) create mode 100644 Assets/Editor/FrameAnimation/FrameAnimationEditorElements.cs create mode 100644 Assets/Editor/FrameAnimation/FrameAnimationEditorElements.cs.meta create mode 100644 Assets/Editor/FrameAnimation/FrameAnimationGraphEditor.uxml create mode 100644 Assets/Editor/FrameAnimation/FrameAnimationGraphEditor.uxml.meta diff --git a/Assets/Editor/FrameAnimation/FrameAnimationEditorElements.cs b/Assets/Editor/FrameAnimation/FrameAnimationEditorElements.cs new file mode 100644 index 000000000..25463b299 --- /dev/null +++ b/Assets/Editor/FrameAnimation/FrameAnimationEditorElements.cs @@ -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 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 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(); + } + } +} diff --git a/Assets/Editor/FrameAnimation/FrameAnimationEditorElements.cs.meta b/Assets/Editor/FrameAnimation/FrameAnimationEditorElements.cs.meta new file mode 100644 index 000000000..dc0efdba2 --- /dev/null +++ b/Assets/Editor/FrameAnimation/FrameAnimationEditorElements.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 876191b33b10eef44b133cc291ebc912 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Editor/FrameAnimation/FrameAnimationGraphEditor.uss b/Assets/Editor/FrameAnimation/FrameAnimationGraphEditor.uss index 0bfb16281..2cd92cbff 100644 --- a/Assets/Editor/FrameAnimation/FrameAnimationGraphEditor.uss +++ b/Assets/Editor/FrameAnimation/FrameAnimationGraphEditor.uss @@ -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; } diff --git a/Assets/Editor/FrameAnimation/FrameAnimationGraphEditor.uxml b/Assets/Editor/FrameAnimation/FrameAnimationGraphEditor.uxml new file mode 100644 index 000000000..508ef28cc --- /dev/null +++ b/Assets/Editor/FrameAnimation/FrameAnimationGraphEditor.uxml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/Assets/Editor/FrameAnimation/FrameAnimationGraphEditor.uxml.meta b/Assets/Editor/FrameAnimation/FrameAnimationGraphEditor.uxml.meta new file mode 100644 index 000000000..f4b6d3311 --- /dev/null +++ b/Assets/Editor/FrameAnimation/FrameAnimationGraphEditor.uxml.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 3286d11c482fe8640a72c689bed6780c +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0} diff --git a/Assets/Editor/FrameAnimation/FrameAnimationGraphEditorWindow.cs b/Assets/Editor/FrameAnimation/FrameAnimationGraphEditorWindow.cs index e9244f119..e833ab639 100644 --- a/Assets/Editor/FrameAnimation/FrameAnimationGraphEditorWindow.cs +++ b/Assets/Editor/FrameAnimation/FrameAnimationGraphEditorWindow.cs @@ -18,13 +18,6 @@ namespace AibisDream.FrameAnimation.Editor private enum BottomTab { ImportDiff, Validation } private enum ValidationKindFilter { All, Graph, Clip, Flow, Source, Node, Edge } - private sealed class BrowserEntry - { - public bool IsHeader; - public string Label; - public FrameAnimationEditorSelection Selection; - } - private const string LastGraphGuidKey = "AibisDream.FrameAnimation.Workspace.LastGraphGuid"; [SerializeField] private FrameAnimationGraph graph; @@ -35,23 +28,26 @@ namespace AibisDream.FrameAnimation.Editor private Label canvasLabel; private FrameAnimationGraphView graphView; private PopupField flowFocusField; - private Label flowFocusStatusLabel; - private ToolbarButton exitFlowFocusButton; private string focusedFlowId = string.Empty; private IReadOnlyList multiSelectedNodes = Array.Empty(); private AnimationNode lastSelectedNode; private Vector2 browserDragStart; private bool browserDragPending; private ListView resourceList; - private readonly List browserEntries = new List(); + private readonly List browserEntries = new List(); private ToolbarSearchField searchField; private EnumField filterField; private EnumField sortField; - private IMGUIContainer propertyContainer; - private IMGUIContainer bottomContainer; + private VisualElement propertyContainer; + private VisualElement bottomContainer; private VisualElement leftPanel; private VisualElement rightPanel; private VisualElement bottomPanel; + private VisualElement globalToolbarHost; + private VisualElement mainHost; + private VisualElement bottomHost; + private readonly List resourceTabButtons = new List(); + private readonly List bottomTabButtons = new List(); private ResourceTab resourceTab; private BottomTab bottomTab; private FrameAnimationClipFilter clipFilter; @@ -75,12 +71,13 @@ namespace AibisDream.FrameAnimation.Editor private readonly List