308 lines
11 KiB
C#
308 lines
11 KiB
C#
using System;
|
||
using System.Linq;
|
||
using UnityEditor;
|
||
using UnityEngine;
|
||
|
||
namespace AibisDream.FrameAnimation.Editor
|
||
{
|
||
internal sealed class FrameAnimationTextInputWindow : EditorWindow
|
||
{
|
||
private string label;
|
||
private string value;
|
||
private string warning;
|
||
private Func<string, string> validate;
|
||
private Action<string> confirm;
|
||
private Vector2 scroll;
|
||
|
||
public static void Show(
|
||
string title,
|
||
string label,
|
||
string initialValue,
|
||
string warning,
|
||
Func<string, string> validate,
|
||
Action<string> confirm)
|
||
{
|
||
var window = CreateInstance<FrameAnimationTextInputWindow>();
|
||
window.titleContent = new GUIContent(title);
|
||
window.label = label;
|
||
window.value = initialValue ?? string.Empty;
|
||
window.warning = warning ?? string.Empty;
|
||
window.validate = validate;
|
||
window.confirm = confirm;
|
||
window.minSize = new Vector2(460f, 220f);
|
||
window.maxSize = new Vector2(700f, 420f);
|
||
window.ShowUtility();
|
||
}
|
||
|
||
private void OnGUI()
|
||
{
|
||
scroll = EditorGUILayout.BeginScrollView(scroll);
|
||
EditorGUILayout.LabelField(label, EditorStyles.boldLabel);
|
||
GUI.SetNextControlName("FrameAnimationTextInput");
|
||
value = EditorGUILayout.TextField(value);
|
||
if (!string.IsNullOrEmpty(warning))
|
||
{
|
||
EditorGUILayout.HelpBox(warning, MessageType.Warning);
|
||
}
|
||
var error = validate?.Invoke(value) ?? string.Empty;
|
||
if (!string.IsNullOrEmpty(error))
|
||
{
|
||
EditorGUILayout.HelpBox(error, MessageType.Error);
|
||
}
|
||
GUILayout.FlexibleSpace();
|
||
EditorGUILayout.BeginHorizontal();
|
||
GUILayout.FlexibleSpace();
|
||
if (GUILayout.Button("取消", GUILayout.Width(90f)))
|
||
{
|
||
Close();
|
||
}
|
||
using (new EditorGUI.DisabledScope(!string.IsNullOrEmpty(error)))
|
||
{
|
||
if (GUILayout.Button("确认", GUILayout.Width(90f)))
|
||
{
|
||
var callback = confirm;
|
||
var result = value;
|
||
Close();
|
||
callback?.Invoke(result);
|
||
}
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
EditorGUILayout.EndScrollView();
|
||
}
|
||
|
||
private void OnEnable()
|
||
{
|
||
EditorApplication.delayCall += FocusInput;
|
||
}
|
||
|
||
private void FocusInput()
|
||
{
|
||
if (this != null)
|
||
{
|
||
Focus();
|
||
EditorGUI.FocusTextInControl("FrameAnimationTextInput");
|
||
}
|
||
}
|
||
}
|
||
|
||
internal sealed class FrameAnimationManualClipWindow : EditorWindow
|
||
{
|
||
private FrameAnimationGraph graph;
|
||
private FrameClip source;
|
||
private string id = "NewClip";
|
||
private string displayName = "New Clip";
|
||
private bool external;
|
||
private Action<FrameClip> completed;
|
||
|
||
public static void Show(
|
||
FrameAnimationGraph graph,
|
||
FrameClip source,
|
||
Action<FrameClip> completed)
|
||
{
|
||
var window = CreateInstance<FrameAnimationManualClipWindow>();
|
||
window.titleContent = new GUIContent(source == null ? "Create Manual Clip" : "Copy As Manual Clip");
|
||
window.graph = graph;
|
||
window.source = source;
|
||
window.completed = completed;
|
||
var baseId = source != null ? source.Id + "_Manual" : "NewClip";
|
||
window.id = MakeUniqueId(graph, baseId);
|
||
window.displayName = source != null ? source.DisplayName + " Manual" : window.id;
|
||
window.minSize = new Vector2(460f, 260f);
|
||
window.maxSize = new Vector2(700f, 420f);
|
||
window.ShowUtility();
|
||
}
|
||
|
||
private void OnGUI()
|
||
{
|
||
EditorGUILayout.LabelField(source == null ? "创建 Manual Clip" : "复制为 Manual Clip", EditorStyles.boldLabel);
|
||
id = EditorGUILayout.TextField("ID", id);
|
||
displayName = EditorGUILayout.TextField("Display Name", displayName);
|
||
external = EditorGUILayout.ToggleLeft("保存为外部独立 .asset(默认保存为 Graph sub-asset)", external);
|
||
var error = FrameAnimationAssetOperations.IsPlayableIdAvailable(graph, id)
|
||
? string.Empty
|
||
: "ID 为空或与当前 Graph 的 Clip / Flow 冲突。";
|
||
if (!string.IsNullOrEmpty(error))
|
||
{
|
||
EditorGUILayout.HelpBox(error, MessageType.Error);
|
||
}
|
||
if (source != null)
|
||
{
|
||
EditorGUILayout.HelpBox(
|
||
"帧表将深拷贝,但 Sprite 仍复用原资源;ImportSource 关联会被清除。",
|
||
MessageType.Info);
|
||
}
|
||
GUILayout.FlexibleSpace();
|
||
EditorGUILayout.BeginHorizontal();
|
||
GUILayout.FlexibleSpace();
|
||
if (GUILayout.Button("取消", GUILayout.Width(90f)))
|
||
{
|
||
Close();
|
||
}
|
||
using (new EditorGUI.DisabledScope(!string.IsNullOrEmpty(error)))
|
||
{
|
||
if (GUILayout.Button("创建", GUILayout.Width(90f)))
|
||
{
|
||
Create();
|
||
}
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
|
||
private void Create()
|
||
{
|
||
var path = string.Empty;
|
||
if (external)
|
||
{
|
||
path = EditorUtility.SaveFilePanelInProject(
|
||
"保存外部 Manual Clip",
|
||
id,
|
||
"asset",
|
||
"请选择 Assets 下的保存位置。");
|
||
if (string.IsNullOrEmpty(path))
|
||
{
|
||
return;
|
||
}
|
||
}
|
||
|
||
var succeeded = source == null
|
||
? FrameAnimationAssetOperations.CreateManualClip(graph, id, displayName, path, out var result, out var error)
|
||
: FrameAnimationAssetOperations.CopyToManual(graph, source, id, displayName, path, out result, out error);
|
||
if (!succeeded)
|
||
{
|
||
EditorUtility.DisplayDialog("操作失败", error, "确定");
|
||
return;
|
||
}
|
||
var callback = completed;
|
||
Close();
|
||
callback?.Invoke(result);
|
||
}
|
||
|
||
private static string MakeUniqueId(FrameAnimationGraph graph, string baseId)
|
||
{
|
||
var candidate = baseId;
|
||
var suffix = 2;
|
||
while (!FrameAnimationAssetOperations.IsPlayableIdAvailable(graph, candidate))
|
||
{
|
||
candidate = baseId + suffix++;
|
||
}
|
||
return candidate;
|
||
}
|
||
}
|
||
|
||
internal sealed class FrameAnimationExistingClipWindow : EditorWindow
|
||
{
|
||
private FrameAnimationGraph graph;
|
||
private FrameClip clip;
|
||
private Action<FrameClip> completed;
|
||
|
||
public static void Show(FrameAnimationGraph graph, Action<FrameClip> completed)
|
||
{
|
||
var window = CreateInstance<FrameAnimationExistingClipWindow>();
|
||
window.titleContent = new GUIContent("Add Existing Manual Clip");
|
||
window.graph = graph;
|
||
window.completed = completed;
|
||
window.minSize = new Vector2(460f, 180f);
|
||
window.maxSize = new Vector2(640f, 260f);
|
||
window.ShowUtility();
|
||
}
|
||
|
||
private void OnGUI()
|
||
{
|
||
EditorGUILayout.LabelField("添加外部 Manual Clip", EditorStyles.boldLabel);
|
||
clip = (FrameClip)EditorGUILayout.ObjectField("FrameClip", clip, typeof(FrameClip), false);
|
||
GUILayout.FlexibleSpace();
|
||
EditorGUILayout.BeginHorizontal();
|
||
GUILayout.FlexibleSpace();
|
||
if (GUILayout.Button("取消", GUILayout.Width(90f)))
|
||
{
|
||
Close();
|
||
}
|
||
using (new EditorGUI.DisabledScope(clip == null))
|
||
{
|
||
if (GUILayout.Button("添加", GUILayout.Width(90f)))
|
||
{
|
||
if (!FrameAnimationAssetOperations.AddExistingManualClip(graph, clip, out var error))
|
||
{
|
||
EditorUtility.DisplayDialog("添加失败", error, "确定");
|
||
return;
|
||
}
|
||
var callback = completed;
|
||
var result = clip;
|
||
Close();
|
||
callback?.Invoke(result);
|
||
}
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
}
|
||
|
||
internal sealed class FrameAnimationFlowWindow : EditorWindow
|
||
{
|
||
private FrameAnimationGraph graph;
|
||
private AnimationNode entry;
|
||
private string id;
|
||
private string displayName;
|
||
private Action<AnimationFlow> completed;
|
||
|
||
internal static void Show(
|
||
FrameAnimationGraph graph,
|
||
AnimationNode entry,
|
||
Action<AnimationFlow> completed)
|
||
{
|
||
var window = CreateInstance<FrameAnimationFlowWindow>();
|
||
window.titleContent = new GUIContent("Create Animation Flow");
|
||
window.graph = graph;
|
||
window.entry = entry;
|
||
window.id = FrameAnimationGraphMutationService.MakeUniqueFlowId(graph, entry);
|
||
window.displayName = window.id;
|
||
window.completed = completed;
|
||
window.minSize = new Vector2(460f, 230f);
|
||
window.maxSize = new Vector2(700f, 360f);
|
||
window.ShowUtility();
|
||
}
|
||
|
||
private void OnGUI()
|
||
{
|
||
EditorGUILayout.LabelField("从选中节点创建 Flow", EditorStyles.boldLabel);
|
||
using (new EditorGUI.DisabledScope(true))
|
||
{
|
||
EditorGUILayout.TextField("Entry Node", entry?.DisplayName ?? string.Empty);
|
||
}
|
||
id = EditorGUILayout.TextField("ID", id);
|
||
displayName = EditorGUILayout.TextField("Display Name", displayName);
|
||
var idError = FrameAnimationAssetOperations.IsPlayableIdAvailable(graph, id)
|
||
? string.Empty
|
||
: "ID 为空或与当前 Graph 中的 Clip / Flow 冲突。";
|
||
var owner = graph?.Flows.FirstOrDefault(flow => flow != null && flow.EntryNodeId == entry?.InternalId);
|
||
if (owner != null)
|
||
{
|
||
idError = $"该节点已是 Flow '{owner.Id}' 的入口。";
|
||
}
|
||
if (!string.IsNullOrEmpty(idError))
|
||
{
|
||
EditorGUILayout.HelpBox(idError, MessageType.Error);
|
||
}
|
||
GUILayout.FlexibleSpace();
|
||
EditorGUILayout.BeginHorizontal();
|
||
GUILayout.FlexibleSpace();
|
||
if (GUILayout.Button("取消", GUILayout.Width(90f))) Close();
|
||
using (new EditorGUI.DisabledScope(!string.IsNullOrEmpty(idError)))
|
||
{
|
||
if (GUILayout.Button("创建", GUILayout.Width(90f)))
|
||
{
|
||
if (!FrameAnimationGraphMutationService.CreateFlow(
|
||
graph, entry, id, displayName, out var flow, out var error))
|
||
{
|
||
EditorUtility.DisplayDialog("创建 Flow 失败", error, "确定");
|
||
return;
|
||
}
|
||
var callback = completed;
|
||
Close();
|
||
callback?.Invoke(flow);
|
||
}
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
}
|
||
}
|