using UnityEngine;
using UnityEditor;
using UnityEditor.Callbacks;
using System;
using System.Linq;
using System.ComponentModel;
namespace System.Runtime.CompilerServices
{
[EditorBrowsable(EditorBrowsableState.Never)]
internal class IsExternalInit { }
}
namespace TagFighter.Dialogue.Editor
{
record DialogueManipulation
{
public record None() : DialogueManipulation();
public record RepositionNode(Vector2 DraggingOffset, DialogueNode DraggingNode) : DialogueManipulation();
public record ResizeNode(Vector2 DraggingOffset, DialogueNode DraggingNode) : DialogueManipulation();
public record RepositionCanvas(Vector2 DraggingOffset) : DialogueManipulation();
public record CreateNode(DialogueNode NodeParent) : DialogueManipulation();
public record DeleteNode(DialogueNode NodeToDelete) : DialogueManipulation();
DialogueManipulation() { }
}
public class DialogueEditor : EditorWindow
{
Dialogue _selectedDialogue = null;
[NonSerialized]
GUIStyle _nodeStyle = null;
[NonSerialized]
GUIStyle _playerNodeStyle = null;
[NonSerialized]
DialogueNode _linkingParentNode = null;
[NonSerialized]
Vector2 _scrollPosition;
[NonSerialized]
DialogueManipulation _manipulationState = new DialogueManipulation.None();
[MenuItem("Window/Dialogue Editor")]
public static void ShowEditorWindow() {
var window = GetWindow<DialogueEditor>();
window.titleContent = new GUIContent("Dialogue");
window.Show();
var dialogueCandidate = Selection.activeObject as Dialogue;
if (dialogueCandidate != null) {
}
}
[OnOpenAsset(1)]
public static bool OnOpenAsset(int instanceID, int line) {
var dialogue = EditorUtility.InstanceIDToObject(instanceID) as Dialogue;
if (dialogue != null) {
ShowEditorWindow();
return true;
}
return false;
}
Selection.selectionChanged += OnSelectionChanged;
_nodeStyle = new();
_nodeStyle.normal.background = EditorGUIUtility.Load("node0") as Texture2D;
_playerNodeStyle = new();
_playerNodeStyle.normal.background = EditorGUIUtility.Load("node1") as Texture2D;
}
Selection.selectionChanged -= OnSelectionChanged;
}
var dialogueCandidate = Selection.activeObject as Dialogue;
if (dialogueCandidate != null) {
}
}
EditorGUILayout.LabelField("Choose A Dialogue", EditorStyles.boldLabel);
}
}
}
switch (_manipulationState, Event.current.type, Event.current.button) {
var clickedNode = GetNodeAtPoint(Event.current.mousePosition + _scrollPosition);
if (clickedNode != null) {
Selection.activeObject = clickedNode;
var dragOffset = clickedNode.Rect.position - Event.current.mousePosition;
new DialogueManipulation.RepositionNode(dragOffset, clickedNode) : new DialogueManipulation.ResizeNode(dragOffset, clickedNode);
}
else {
_manipulationState = new DialogueManipulation.RepositionCanvas(Event.current.mousePosition + _scrollPosition);
}
break;
state.DraggingNode.Position = Event.current.mousePosition + state.DraggingOffset;
GUI.changed = true;
break;
state.DraggingNode.Size = Event.current.mousePosition - state.DraggingNode.Position;
GUI.changed = true;
break;
_scrollPosition = state.DraggingOffset - Event.current.mousePosition;
GUI.changed = true;
break;
_manipulationState = new DialogueManipulation.None();
break;
}
}
}
GUILayout.BeginArea(node.Rect, nodeStyle);
node.Text = EditorGUILayout.TextField(node.Text);
GUILayout.BeginHorizontal();
if (GUILayout.Button("x")) {
_manipulationState = new DialogueManipulation.DeleteNode(node);
}
DrawLinkButtons(node);
GUI.enabled = true;
if (GUILayout.Button("+")) {
_manipulationState = new DialogueManipulation.CreateNode(node);
}
GUILayout.EndHorizontal();
GUILayout.EndArea();
}
if (_linkingParentNode == null) {
if (GUILayout.Button("link")) {
_linkingParentNode = node;
}
}
else if (_linkingParentNode == node) {
if (GUILayout.Button("cancel")) {
_linkingParentNode = null;
}
}
else if (_linkingParentNode.GetChildren().Contains(node.name)) {
if (GUILayout.Button("unlink")) {
_linkingParentNode.RemoveChild(node.name);
_linkingParentNode = null;
}
}
else if (GUILayout.Button("child")) {
_linkingParentNode.AddChild(node.name);
_linkingParentNode = null;
}
}
Vector3 startPosition = new Vector2(node.Rect.xMax, node.Rect.center.y);
Vector3 endPosition = new Vector2(childNode.Rect.xMin, childNode.Rect.center.y);
controlPointOffset.y = 0;
controlPointOffset.x *= 0.8f;
Handles.DrawBezier(startPosition, endPosition, startPosition + controlPointOffset, endPosition - controlPointOffset, Color.white, null, 4f);
}
}
}
}
var controlPointOffset = endPosition - startPosition;
foreach (var childNode in node.GetChildren().Select(name => _nodeIndex[name])) {
void DrawConnections(DialogueNode node) {
void DrawLinkButtons(DialogueNode node) {
GUI.enabled = SelectedDialogue.Nodes.Count > 1;
void DrawNode(DialogueNode node) {
var nodeStyle = node.IsPlayerSpeaking ? _playerNodeStyle : _nodeStyle;
DialogueNode GetNodeAtPoint(Vector2 point) {
return SelectedDialogue.Nodes.Where(node => node.Rect.Contains(point)).LastOrDefault();
case (_, EventType.MouseUp, LeftMouseButton):
case (DialogueManipulation.RepositionCanvas state, EventType.MouseDrag, LeftMouseButton):
case (DialogueManipulation.ResizeNode state, EventType.MouseDrag, LeftMouseButton):
case (DialogueManipulation.RepositionNode state, EventType.MouseDrag, LeftMouseButton):
Selection.activeObject = SelectedDialogue;
_manipulationState = (clickedNode.Rect.height + dragOffset.y > ResizeBorder) || (clickedNode.Rect.width + dragOffset.x > ResizeBorder) ?
case (DialogueManipulation.None, EventType.MouseDown, LeftMouseButton):
void ProcessEvents() {
foreach (var (start, end) in NodeSides()) {
Handles.DrawBezier(start, end, start, end, Color.white, null, 4f);
yield return (topLeft, topRight);
yield return (topRight, bottomRight);
yield return (bottomRight, bottomLeft);
yield return (bottomLeft, topLeft);
}
void OutlineRootNode(DialogueNode dialogueNode) {
IEnumerable<(Vector3 start, Vector3 end)> NodeSides() {
var drawOffset = 2f;
Vector3 topLeft = new Vector2(dialogueNode.Rect.xMin - drawOffset, dialogueNode.Rect.yMin - drawOffset);
Vector3 topRight = new Vector2(dialogueNode.Rect.xMax + drawOffset, dialogueNode.Rect.yMin - drawOffset);
Vector3 bottomLeft = new Vector2(dialogueNode.Rect.xMin - drawOffset, dialogueNode.Rect.yMax + drawOffset);
Vector3 bottomRight = new Vector2(dialogueNode.Rect.xMax + drawOffset, dialogueNode.Rect.yMax + drawOffset);
void DrawDialogueName() {
GUILayout.BeginArea(new Rect(_scrollPosition.x, _scrollPosition.y, CanvasSize, 20));
EditorGUILayout.LabelField(SelectedDialogue.name, EditorStyles.boldLabel);
GUILayout.EndArea();
}
DrawDialogueName();
EditorGUILayout.EndScrollView();
switch (_manipulationState) {
case DialogueManipulation.CreateNode action:
SelectedDialogue.CreateNode(action.NodeParent);
_manipulationState = new DialogueManipulation.None();
break;
case DialogueManipulation.DeleteNode action:
SelectedDialogue.DeleteNode(action.NodeToDelete);
_manipulationState = new DialogueManipulation.None();
break;
}
}
ProcessEvents();
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
var canvas = GUILayoutUtility.GetRect(CanvasSize, CanvasSize);
var backgroundTex = UnityEngine.Resources.Load("background") as Texture2D;
Rect texCoords = new(0, 0, CanvasSize / backgroundTex.width, CanvasSize / backgroundTex.height);
GUI.DrawTextureWithTexCoords(canvas, backgroundTex, texCoords);
_nodeIndex = SelectedDialogue.Nodes.ToDictionary(node => node.name);
foreach (var node in SelectedDialogue.Nodes) {
DrawConnections(node);
}
OutlineRootNode(SelectedDialogue.Nodes.First());
foreach (var node in SelectedDialogue.Nodes) {
DrawNode(node);
}
return;
void OnGUI() {
if (SelectedDialogue == null) {
SelectedDialogue = dialogueCandidate;
void OnSelectionChanged() {
void OnDisable() {
_playerNodeStyle.padding = padding;
_playerNodeStyle.border = border;
_playerNodeStyle.overflow = overflow;
_nodeStyle.padding = padding;
_nodeStyle.border = border;
_nodeStyle.overflow = overflow;
var padding = new RectOffset(12, 12, 12, 12);
var border = new RectOffset(12, 12, 12, 12);
var overflow = new RectOffset(7, 7, 7, 9);
void OnEnable() {
window.SelectedDialogue = dialogueCandidate;
const int LeftMouseButton = 0;
const float CanvasSize = 4000;
const int ResizeBorder = 8;
[NonSerialized]
Dictionary<string, DialogueNode> _nodeIndex;
Dialogue SelectedDialogue {
get {
return _selectedDialogue;
}
set {
if (_selectedDialogue != value) {
_selectedDialogue = value;
if (_selectedDialogue.Nodes.Count == 0) {
var rootDefaultPosition = new Vector2(5, 25);
_selectedDialogue.CreateNode(null).Position = rootDefaultPosition;
}
Repaint();
}
}
}
using System.Collections.Generic;