283 lines
8.8 KiB
C#
283 lines
8.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using AibisDream;
|
|
using UnityEditor;
|
|
using UnityEditor.Experimental.GraphView;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
|
|
namespace AibisDream.SystemEditor
|
|
{
|
|
public class ChapterNode : Node
|
|
{
|
|
public TalkSceneSO SceneSo { get; }
|
|
public Port InputPort { get; private set; }
|
|
|
|
private const float PortLabelMaxWidth = 120f;
|
|
|
|
public ChapterNode(TalkSceneSO so)
|
|
{
|
|
SceneSo = so ?? throw new ArgumentNullException(nameof(so));
|
|
|
|
// 节点整体放大
|
|
style.minWidth = 240;
|
|
style.paddingTop = 8;
|
|
style.paddingBottom = 8;
|
|
style.paddingLeft = 10;
|
|
style.paddingRight = 10;
|
|
|
|
UpdateTitle();
|
|
UpdateColor();
|
|
BuildInputPort();
|
|
BuildOutputPorts();
|
|
BuildSubtitle();
|
|
BuildAddExitButton();
|
|
SetPosition(new Rect(so.graphPosition, Vector2.zero));
|
|
|
|
// 放大标题
|
|
var titleLabel = this.Q<Label>("title-label");
|
|
if (titleLabel != null)
|
|
{
|
|
titleLabel.style.fontSize = 16;
|
|
titleLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
|
|
}
|
|
}
|
|
|
|
public void UpdateTitle()
|
|
{
|
|
title = string.IsNullOrEmpty(SceneSo.title) ? SceneSo.name : SceneSo.title;
|
|
}
|
|
|
|
public override void BuildContextualMenu(ContextualMenuPopulateEvent evt)
|
|
{
|
|
base.BuildContextualMenu(evt);
|
|
|
|
if (SceneSo.exits != null && SceneSo.exits.Count > 0)
|
|
{
|
|
for (int i = 0; i < SceneSo.exits.Count; i++)
|
|
{
|
|
var exit = SceneSo.exits[i];
|
|
var name = string.IsNullOrEmpty(exit.exitName) ? $"出口{i}" : exit.exitName;
|
|
var idx = i;
|
|
evt.menu.AppendAction($"删除出口/{name}", _ => RemoveExitAt(idx));
|
|
}
|
|
}
|
|
}
|
|
|
|
public void UpdateColor()
|
|
{
|
|
style.backgroundColor = new StyleColor(SceneSo.nodeColor);
|
|
}
|
|
|
|
private void BuildInputPort()
|
|
{
|
|
InputPort = Port.Create<Edge>(
|
|
Orientation.Horizontal,
|
|
Direction.Input,
|
|
Port.Capacity.Single,
|
|
typeof(TalkSceneSO));
|
|
InputPort.portName = "▶";
|
|
InputPort.style.fontSize = 14;
|
|
InputPort.AddManipulator(new EdgeConnector<Edge>(new EmptyEdgeListener()));
|
|
inputContainer.Add(InputPort);
|
|
}
|
|
|
|
private void BuildOutputPorts()
|
|
{
|
|
outputContainer.Clear();
|
|
if (SceneSo.exits == null) return;
|
|
|
|
for (int i = 0; i < SceneSo.exits.Count; i++)
|
|
{
|
|
var exit = SceneSo.exits[i];
|
|
var port = CreateOutputPort(exit, i);
|
|
outputContainer.Add(port);
|
|
}
|
|
}
|
|
|
|
private Port CreateOutputPort(SceneExit exit, int index)
|
|
{
|
|
var port = Port.Create<Edge>(
|
|
Orientation.Horizontal,
|
|
Direction.Output,
|
|
Port.Capacity.Single,
|
|
typeof(TalkSceneSO));
|
|
|
|
port.portName = string.IsNullOrEmpty(exit.exitName) ? $"出口{index}" : exit.exitName;
|
|
port.userData = index;
|
|
|
|
// 限制端口标签宽度并放大字体
|
|
var label = port.Q<Label>("type");
|
|
if (label != null)
|
|
{
|
|
label.style.maxWidth = PortLabelMaxWidth;
|
|
label.style.fontSize = 12;
|
|
}
|
|
|
|
port.AddManipulator(new EdgeConnector<Edge>(new EmptyEdgeListener()));
|
|
|
|
port.RegisterCallback<ContextualMenuPopulateEvent>((evt) =>
|
|
{
|
|
var currentIndex = port.userData is int idx ? idx : -1;
|
|
evt.menu.AppendAction("删除出口", (_) => RemoveExitAt(currentIndex));
|
|
}, TrickleDown.TrickleDown);
|
|
|
|
return port;
|
|
}
|
|
|
|
private void BuildSubtitle()
|
|
{
|
|
var subtitleLabel = new Label($"{SceneSo.name}")
|
|
{
|
|
style =
|
|
{
|
|
fontSize = 12,
|
|
unityTextAlign = TextAnchor.MiddleCenter,
|
|
color = new Color(0.8f, 0.8f, 0.8f),
|
|
paddingTop = 4,
|
|
paddingBottom = 2
|
|
}
|
|
};
|
|
extensionContainer.Add(subtitleLabel);
|
|
RefreshExpandedState();
|
|
}
|
|
|
|
private void BuildAddExitButton()
|
|
{
|
|
var removeButton = new Button(() => RemoveLastExit())
|
|
{
|
|
text = "-",
|
|
style =
|
|
{
|
|
width = 26,
|
|
height = 22,
|
|
fontSize = 16,
|
|
unityFontStyleAndWeight = FontStyle.Bold,
|
|
marginRight = 4
|
|
}
|
|
};
|
|
titleButtonContainer.Add(removeButton);
|
|
|
|
var addButton = new Button(() => AddExit())
|
|
{
|
|
text = "+",
|
|
style = { width = 26, height = 22, fontSize = 16, unityFontStyleAndWeight = FontStyle.Bold }
|
|
};
|
|
titleButtonContainer.Add(addButton);
|
|
}
|
|
|
|
private void RemoveLastExit()
|
|
{
|
|
if (SceneSo.exits == null || SceneSo.exits.Count == 0) return;
|
|
RemoveExitAt(SceneSo.exits.Count - 1);
|
|
}
|
|
|
|
public void AddExit()
|
|
{
|
|
Undo.RecordObject(SceneSo, "Add Exit");
|
|
if (SceneSo.exits == null)
|
|
SceneSo.exits = new System.Collections.Generic.List<SceneExit>();
|
|
|
|
var newExit = new SceneExit
|
|
{
|
|
exitName = $"exit_{SceneSo.exits.Count}"
|
|
};
|
|
SceneSo.exits.Add(newExit);
|
|
EditorUtility.SetDirty(SceneSo);
|
|
|
|
var newPort = CreateOutputPort(newExit, SceneSo.exits.Count - 1);
|
|
outputContainer.Add(newPort);
|
|
RefreshPorts();
|
|
}
|
|
|
|
public void RemoveExitAt(int index)
|
|
{
|
|
if (SceneSo.exits == null || index < 0 || index >= SceneSo.exits.Count) return;
|
|
|
|
// 断开并移除该端口的所有边
|
|
var portToRemove = GetOutputPort(index);
|
|
if (portToRemove != null)
|
|
{
|
|
var edges = portToRemove.connections.ToList();
|
|
foreach (var edge in edges)
|
|
{
|
|
edge.input?.Disconnect(edge);
|
|
edge.output?.Disconnect(edge);
|
|
edge.RemoveFromHierarchy();
|
|
}
|
|
portToRemove.RemoveFromHierarchy();
|
|
}
|
|
|
|
Undo.RecordObject(SceneSo, "Remove Exit");
|
|
SceneSo.exits.RemoveAt(index);
|
|
EditorUtility.SetDirty(SceneSo);
|
|
|
|
// 更新后续端口的 userData 和 portName
|
|
for (int i = index; i < SceneSo.exits.Count; i++)
|
|
{
|
|
var port = GetOutputPort(i);
|
|
if (port != null)
|
|
{
|
|
port.userData = i;
|
|
var exit = SceneSo.exits[i];
|
|
port.portName = string.IsNullOrEmpty(exit.exitName) ? $"出口{i}" : exit.exitName;
|
|
}
|
|
}
|
|
|
|
RefreshPorts();
|
|
}
|
|
|
|
public void RefreshOutputPorts()
|
|
{
|
|
// 先断开所有输出端口的边
|
|
var edgesToRemove = outputContainer.Children()
|
|
.OfType<Port>()
|
|
.SelectMany(p => p.connections.ToList())
|
|
.ToList();
|
|
|
|
foreach (var edge in edgesToRemove)
|
|
{
|
|
edge.input?.Disconnect(edge);
|
|
edge.output?.Disconnect(edge);
|
|
edge.RemoveFromHierarchy();
|
|
}
|
|
|
|
BuildOutputPorts();
|
|
RefreshPorts();
|
|
}
|
|
|
|
public Port GetOutputPort(int index)
|
|
{
|
|
return outputContainer.Children().OfType<Port>().ElementAtOrDefault(index);
|
|
}
|
|
|
|
public override void SetPosition(Rect newPos)
|
|
{
|
|
base.SetPosition(newPos);
|
|
if (SceneSo != null)
|
|
{
|
|
SceneSo.graphPosition = newPos.position;
|
|
EditorUtility.SetDirty(SceneSo);
|
|
}
|
|
}
|
|
|
|
public void SyncPositionToSo()
|
|
{
|
|
if (SceneSo == null) return;
|
|
var pos = GetPosition();
|
|
if (SceneSo.graphPosition != pos.position)
|
|
{
|
|
SceneSo.graphPosition = pos.position;
|
|
EditorUtility.SetDirty(SceneSo);
|
|
}
|
|
}
|
|
|
|
private class EmptyEdgeListener : IEdgeConnectorListener
|
|
{
|
|
public void OnDropOutsidePort(Edge edge, Vector2 position) { }
|
|
public void OnDrop(GraphView graphView, Edge edge) { }
|
|
}
|
|
}
|
|
}
|