mirror of
https://github.com/Siccity/xNode.git
synced 2026-02-06 07:14:56 +08:00
Merge branch 'refs/heads/master' into examples
This commit is contained in:
commit
4c824d4b91
@ -72,8 +72,6 @@ namespace XNodeEditor {
|
||||
GUIHelper.ClearRepaintRequest();
|
||||
window.Repaint();
|
||||
}
|
||||
#else
|
||||
window.Repaint();
|
||||
#endif
|
||||
|
||||
#if ODIN_INSPECTOR
|
||||
|
||||
@ -81,6 +81,7 @@ namespace XNodeEditor {
|
||||
for (int i = 0; i < Selection.objects.Length; i++) {
|
||||
if (Selection.objects[i] is XNode.Node) {
|
||||
XNode.Node node = Selection.objects[i] as XNode.Node;
|
||||
Undo.RecordObject(node, "Moved Node");
|
||||
Vector2 initial = node.position;
|
||||
node.position = mousePos + dragOffset[i];
|
||||
if (gridSnap) {
|
||||
@ -274,13 +275,13 @@ namespace XNodeEditor {
|
||||
ShowPortContextMenu(hoveredPort);
|
||||
} else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) {
|
||||
if (!Selection.Contains(hoveredNode)) SelectNode(hoveredNode, false);
|
||||
autoConnectOutput = null;
|
||||
autoConnectOutput = null;
|
||||
GenericMenu menu = new GenericMenu();
|
||||
NodeEditor.GetEditor(hoveredNode, this).AddContextMenuItems(menu);
|
||||
menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
|
||||
e.Use(); // Fixes copy/paste context menu appearing in Unity 5.6.6f2 - doesn't occur in 2018.3.2f1 Probably needs to be used in other places.
|
||||
} else if (!IsHoveringNode) {
|
||||
autoConnectOutput = null;
|
||||
autoConnectOutput = null;
|
||||
GenericMenu menu = new GenericMenu();
|
||||
graphEditor.AddContextMenuItems(menu);
|
||||
menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
|
||||
@ -309,6 +310,7 @@ namespace XNodeEditor {
|
||||
SelectNode(node, true);
|
||||
}
|
||||
}
|
||||
Repaint();
|
||||
}
|
||||
break;
|
||||
case EventType.ValidateCommand:
|
||||
@ -412,6 +414,7 @@ namespace XNodeEditor {
|
||||
public void DuplicateSelectedNodes() {
|
||||
// Get selected nodes which are part of this graph
|
||||
XNode.Node[] selectedNodes = Selection.objects.Select(x => x as XNode.Node).Where(x => x != null && x.graph == graph).ToArray();
|
||||
if (selectedNodes == null || selectedNodes.Length == 0) return;
|
||||
// Get top left node position
|
||||
Vector2 topLeftNode = selectedNodes.Select(x => x.position).Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y)));
|
||||
InsertDuplicateNodes(selectedNodes, topLeftNode + new Vector2(30, 30));
|
||||
@ -470,8 +473,10 @@ namespace XNodeEditor {
|
||||
/// <summary> Draw a connection as we are dragging it </summary>
|
||||
public void DrawDraggedConnection() {
|
||||
if (IsDraggingPort) {
|
||||
Color col = NodeEditorPreferences.GetTypeColor(draggedOutput.ValueType);
|
||||
col.a = draggedOutputTarget != null ? 1.0f : 0.6f;
|
||||
Gradient gradient = graphEditor.GetNoodleGradient(draggedOutput, null);
|
||||
float thickness = graphEditor.GetNoodleThickness(draggedOutput, null);
|
||||
NoodlePath path = graphEditor.GetNoodlePath(draggedOutput, null);
|
||||
NoodleStroke stroke = graphEditor.GetNoodleStroke(draggedOutput, null);
|
||||
|
||||
Rect fromRect;
|
||||
if (!_portConnectionPoints.TryGetValue(draggedOutput, out fromRect)) return;
|
||||
@ -483,10 +488,10 @@ namespace XNodeEditor {
|
||||
if (draggedOutputTarget != null) gridPoints.Add(portConnectionPoints[draggedOutputTarget].center);
|
||||
else gridPoints.Add(WindowToGridPosition(Event.current.mousePosition));
|
||||
|
||||
DrawNoodle(col, gridPoints);
|
||||
DrawNoodle(gradient, path, stroke, thickness, gridPoints);
|
||||
|
||||
Color bgcol = Color.black;
|
||||
Color frcol = col;
|
||||
Color frcol = gradient.colorKeys[0].color;
|
||||
bgcol.a = 0.6f;
|
||||
frcol.a = 0.6f;
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using System.IO;
|
||||
|
||||
namespace XNodeEditor {
|
||||
/// <summary> Deals with modified assets </summary>
|
||||
@ -9,6 +10,9 @@ namespace XNodeEditor {
|
||||
/// This is important to do, because you can't delete null sub assets.
|
||||
/// <para/> For another workaround, see: https://gitlab.com/RotaryHeart-UnityShare/subassetmissingscriptdelete </summary>
|
||||
private static AssetDeleteResult OnWillDeleteAsset (string path, RemoveAssetOptions options) {
|
||||
// Skip processing anything without the .cs extension
|
||||
if (Path.GetExtension(path) != ".cs") return AssetDeleteResult.DidNotDelete;
|
||||
|
||||
// Get the object that is requested for deletion
|
||||
UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath<UnityEngine.Object> (path);
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ namespace XNodeEditor {
|
||||
private int topPadding { get { return isDocked() ? 19 : 22; } }
|
||||
/// <summary> Executed after all other window GUI. Useful if Zoom is ruining your day. Automatically resets after being run.</summary>
|
||||
public event Action onLateGUI;
|
||||
private static readonly Vector3[] polyLineTempArray = new Vector3[2];
|
||||
|
||||
private void OnGUI() {
|
||||
Event e = Event.current;
|
||||
@ -116,69 +117,153 @@ namespace XNodeEditor {
|
||||
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
|
||||
}
|
||||
|
||||
static Vector2 CalculateBezierPoint(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float t) {
|
||||
float u = 1 - t;
|
||||
float tt = t * t, uu = u * u;
|
||||
float uuu = uu * u, ttt = tt * t;
|
||||
return new Vector2(
|
||||
(uuu * p0.x) + (3 * uu * t * p1.x) + (3 * u * tt * p2.x) + (ttt * p3.x),
|
||||
(uuu * p0.y) + (3 * uu * t * p1.y) + (3 * u * tt * p2.y) + (ttt * p3.y)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary> Draws a line segment without allocating temporary arrays </summary>
|
||||
static void DrawAAPolyLineNonAlloc(float thickness, Vector2 p0, Vector2 p1) {
|
||||
polyLineTempArray[0].x = p0.x;
|
||||
polyLineTempArray[0].y = p0.y;
|
||||
polyLineTempArray[1].x = p1.x;
|
||||
polyLineTempArray[1].y = p1.y;
|
||||
Handles.DrawAAPolyLine(thickness, polyLineTempArray);
|
||||
}
|
||||
|
||||
/// <summary> Draw a bezier from output to input in grid coordinates </summary>
|
||||
public void DrawNoodle(Color col, List<Vector2> gridPoints) {
|
||||
Vector2[] windowPoints = gridPoints.Select(x => GridToWindowPosition(x)).ToArray();
|
||||
Handles.color = col;
|
||||
public void DrawNoodle(Gradient gradient, NoodlePath path, NoodleStroke stroke, float thickness, List<Vector2> gridPoints) {
|
||||
// convert grid points to window points
|
||||
for (int i = 0; i < gridPoints.Count; ++i)
|
||||
gridPoints[i] = GridToWindowPosition(gridPoints[i]);
|
||||
|
||||
Handles.color = gradient.Evaluate(0f);
|
||||
int length = gridPoints.Count;
|
||||
switch (NodeEditorPreferences.GetSettings().noodleType) {
|
||||
case NodeEditorPreferences.NoodleType.Curve:
|
||||
switch (path) {
|
||||
case NoodlePath.Curvy:
|
||||
Vector2 outputTangent = Vector2.right;
|
||||
for (int i = 0; i < length - 1; i++) {
|
||||
Vector2 inputTangent = Vector2.left;
|
||||
|
||||
if (i == 0) outputTangent = Vector2.right * Vector2.Distance(windowPoints[i], windowPoints[i + 1]) * 0.01f * zoom;
|
||||
Vector2 inputTangent;
|
||||
// Cached most variables that repeat themselves here to avoid so many indexer calls :p
|
||||
Vector2 point_a = gridPoints[i];
|
||||
Vector2 point_b = gridPoints[i + 1];
|
||||
float dist_ab = Vector2.Distance(point_a, point_b);
|
||||
if (i == 0) outputTangent = zoom * dist_ab * 0.01f * Vector2.right;
|
||||
if (i < length - 2) {
|
||||
Vector2 ab = (windowPoints[i + 1] - windowPoints[i]).normalized;
|
||||
Vector2 cb = (windowPoints[i + 1] - windowPoints[i + 2]).normalized;
|
||||
Vector2 ac = (windowPoints[i + 2] - windowPoints[i]).normalized;
|
||||
Vector2 point_c = gridPoints[i + 2];
|
||||
Vector2 ab = (point_b - point_a).normalized;
|
||||
Vector2 cb = (point_b - point_c).normalized;
|
||||
Vector2 ac = (point_c - point_a).normalized;
|
||||
Vector2 p = (ab + cb) * 0.5f;
|
||||
float tangentLength = (Vector2.Distance(windowPoints[i], windowPoints[i + 1]) + Vector2.Distance(windowPoints[i + 1], windowPoints[i + 2])) * 0.005f * zoom;
|
||||
float side = ((ac.x * (windowPoints[i + 1].y - windowPoints[i].y)) - (ac.y * (windowPoints[i + 1].x - windowPoints[i].x)));
|
||||
float tangentLength = (dist_ab + Vector2.Distance(point_b, point_c)) * 0.005f * zoom;
|
||||
float side = ((ac.x * (point_b.y - point_a.y)) - (ac.y * (point_b.x - point_a.x)));
|
||||
|
||||
p = new Vector2(-p.y, p.x) * Mathf.Sign(side) * tangentLength;
|
||||
p = tangentLength * Mathf.Sign(side) * new Vector2(-p.y, p.x);
|
||||
inputTangent = p;
|
||||
}
|
||||
else {
|
||||
inputTangent = Vector2.left * Vector2.Distance(windowPoints[i], windowPoints[i + 1]) * 0.01f * zoom;
|
||||
} else {
|
||||
inputTangent = zoom * dist_ab * 0.01f * Vector2.left;
|
||||
}
|
||||
|
||||
Handles.DrawBezier(windowPoints[i], windowPoints[i + 1], windowPoints[i] + ((outputTangent * 50) / zoom), windowPoints[i + 1] + ((inputTangent * 50) / zoom), col, null, 4);
|
||||
// Calculates the tangents for the bezier's curves.
|
||||
float zoomCoef = 50 / zoom;
|
||||
Vector2 tangent_a = point_a + outputTangent * zoomCoef;
|
||||
Vector2 tangent_b = point_b + inputTangent * zoomCoef;
|
||||
// Hover effect.
|
||||
int division = Mathf.RoundToInt(.2f * dist_ab) + 3;
|
||||
// Coloring and bezier drawing.
|
||||
int draw = 0;
|
||||
Vector2 bezierPrevious = point_a;
|
||||
for (int j = 1; j <= division; ++j) {
|
||||
if (stroke == NoodleStroke.Dashed) {
|
||||
draw++;
|
||||
if (draw >= 2) draw = -2;
|
||||
if (draw < 0) continue;
|
||||
if (draw == 0) bezierPrevious = CalculateBezierPoint(point_a, tangent_a, tangent_b, point_b, (j - 1f) / (float) division);
|
||||
}
|
||||
if (i == length - 2)
|
||||
Handles.color = gradient.Evaluate((j + 1f) / division);
|
||||
Vector2 bezierNext = CalculateBezierPoint(point_a, tangent_a, tangent_b, point_b, j / (float) division);
|
||||
DrawAAPolyLineNonAlloc(thickness, bezierPrevious, bezierNext);
|
||||
bezierPrevious = bezierNext;
|
||||
}
|
||||
outputTangent = -inputTangent;
|
||||
}
|
||||
break;
|
||||
case NodeEditorPreferences.NoodleType.Line:
|
||||
case NoodlePath.Straight:
|
||||
for (int i = 0; i < length - 1; i++) {
|
||||
Handles.DrawAAPolyLine(5, windowPoints[i], windowPoints[i + 1]);
|
||||
Vector2 point_a = gridPoints[i];
|
||||
Vector2 point_b = gridPoints[i + 1];
|
||||
// Draws the line with the coloring.
|
||||
Vector2 prev_point = point_a;
|
||||
// Approximately one segment per 5 pixels
|
||||
int segments = (int) Vector2.Distance(point_a, point_b) / 5;
|
||||
|
||||
int draw = 0;
|
||||
for (int j = 0; j <= segments; j++) {
|
||||
draw++;
|
||||
float t = j / (float) segments;
|
||||
Vector2 lerp = Vector2.Lerp(point_a, point_b, t);
|
||||
if (draw > 0) {
|
||||
if (i == length - 2) Handles.color = gradient.Evaluate(t);
|
||||
DrawAAPolyLineNonAlloc(thickness, prev_point, lerp);
|
||||
}
|
||||
prev_point = lerp;
|
||||
if (stroke == NoodleStroke.Dashed && draw >= 2) draw = -2;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NodeEditorPreferences.NoodleType.Angled:
|
||||
case NoodlePath.Angled:
|
||||
for (int i = 0; i < length - 1; i++) {
|
||||
if (i == length - 1) continue; // Skip last index
|
||||
if (windowPoints[i].x <= windowPoints[i + 1].x - (50 / zoom)) {
|
||||
float midpoint = (windowPoints[i].x + windowPoints[i + 1].x) * 0.5f;
|
||||
Vector2 start_1 = windowPoints[i];
|
||||
Vector2 end_1 = windowPoints[i + 1];
|
||||
if (gridPoints[i].x <= gridPoints[i + 1].x - (50 / zoom)) {
|
||||
float midpoint = (gridPoints[i].x + gridPoints[i + 1].x) * 0.5f;
|
||||
Vector2 start_1 = gridPoints[i];
|
||||
Vector2 end_1 = gridPoints[i + 1];
|
||||
start_1.x = midpoint;
|
||||
end_1.x = midpoint;
|
||||
Handles.DrawAAPolyLine(5, windowPoints[i], start_1);
|
||||
Handles.DrawAAPolyLine(5, start_1, end_1);
|
||||
Handles.DrawAAPolyLine(5, end_1, windowPoints[i + 1]);
|
||||
if (i == length - 2) {
|
||||
DrawAAPolyLineNonAlloc(thickness, gridPoints[i], start_1);
|
||||
Handles.color = gradient.Evaluate(0.5f);
|
||||
DrawAAPolyLineNonAlloc(thickness, start_1, end_1);
|
||||
Handles.color = gradient.Evaluate(1f);
|
||||
DrawAAPolyLineNonAlloc(thickness, end_1, gridPoints[i + 1]);
|
||||
} else {
|
||||
DrawAAPolyLineNonAlloc(thickness, gridPoints[i], start_1);
|
||||
DrawAAPolyLineNonAlloc(thickness, start_1, end_1);
|
||||
DrawAAPolyLineNonAlloc(thickness, end_1, gridPoints[i + 1]);
|
||||
}
|
||||
} else {
|
||||
float midpoint = (windowPoints[i].y + windowPoints[i + 1].y) * 0.5f;
|
||||
Vector2 start_1 = windowPoints[i];
|
||||
Vector2 end_1 = windowPoints[i + 1];
|
||||
float midpoint = (gridPoints[i].y + gridPoints[i + 1].y) * 0.5f;
|
||||
Vector2 start_1 = gridPoints[i];
|
||||
Vector2 end_1 = gridPoints[i + 1];
|
||||
start_1.x += 25 / zoom;
|
||||
end_1.x -= 25 / zoom;
|
||||
Vector2 start_2 = start_1;
|
||||
Vector2 end_2 = end_1;
|
||||
start_2.y = midpoint;
|
||||
end_2.y = midpoint;
|
||||
Handles.DrawAAPolyLine(5, windowPoints[i], start_1);
|
||||
Handles.DrawAAPolyLine(5, start_1, start_2);
|
||||
Handles.DrawAAPolyLine(5, start_2, end_2);
|
||||
Handles.DrawAAPolyLine(5, end_2, end_1);
|
||||
Handles.DrawAAPolyLine(5, end_1, windowPoints[i + 1]);
|
||||
if (i == length - 2) {
|
||||
DrawAAPolyLineNonAlloc(thickness, gridPoints[i], start_1);
|
||||
Handles.color = gradient.Evaluate(0.25f);
|
||||
DrawAAPolyLineNonAlloc(thickness, start_1, start_2);
|
||||
Handles.color = gradient.Evaluate(0.5f);
|
||||
DrawAAPolyLineNonAlloc(thickness, start_2, end_2);
|
||||
Handles.color = gradient.Evaluate(0.75f);
|
||||
DrawAAPolyLineNonAlloc(thickness, end_2, end_1);
|
||||
Handles.color = gradient.Evaluate(1f);
|
||||
DrawAAPolyLineNonAlloc(thickness, end_1, gridPoints[i + 1]);
|
||||
} else {
|
||||
DrawAAPolyLineNonAlloc(thickness, gridPoints[i], start_1);
|
||||
DrawAAPolyLineNonAlloc(thickness, start_1, start_2);
|
||||
DrawAAPolyLineNonAlloc(thickness, start_2, end_2);
|
||||
DrawAAPolyLineNonAlloc(thickness, end_2, end_1);
|
||||
DrawAAPolyLineNonAlloc(thickness, end_1, gridPoints[i + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -191,6 +276,8 @@ namespace XNodeEditor {
|
||||
List<RerouteReference> selection = preBoxSelectionReroute != null ? new List<RerouteReference>(preBoxSelectionReroute) : new List<RerouteReference>();
|
||||
hoveredReroute = new RerouteReference();
|
||||
|
||||
List<Vector2> gridPoints = new List<Vector2>(2);
|
||||
|
||||
Color col = GUI.color;
|
||||
foreach (XNode.Node node in graph.nodes) {
|
||||
//If a null node is found, return. This can happen if the nodes associated script is deleted. It is currently not possible in Unity to delete a null asset.
|
||||
@ -202,10 +289,14 @@ namespace XNodeEditor {
|
||||
Rect fromRect;
|
||||
if (!_portConnectionPoints.TryGetValue(output, out fromRect)) continue;
|
||||
|
||||
Color portColor = graphEditor.GetPortColor(output);
|
||||
for (int k = 0; k < output.ConnectionCount; k++) {
|
||||
XNode.NodePort input = output.GetConnection(k);
|
||||
|
||||
Color noodleColor = graphEditor.GetNoodleColor(output, input);
|
||||
Gradient noodleGradient = graphEditor.GetNoodleGradient(output, input);
|
||||
float noodleThickness = graphEditor.GetNoodleThickness(output, input);
|
||||
NoodlePath noodlePath = graphEditor.GetNoodlePath(output, input);
|
||||
NoodleStroke noodleStroke = graphEditor.GetNoodleStroke(output, input);
|
||||
|
||||
// Error handling
|
||||
if (input == null) continue; //If a script has been updated and the port doesn't exist, it is removed and null is returned. If this happens, return.
|
||||
@ -215,11 +306,11 @@ namespace XNodeEditor {
|
||||
|
||||
List<Vector2> reroutePoints = output.GetReroutePoints(k);
|
||||
|
||||
List<Vector2> gridPoints = new List<Vector2>();
|
||||
gridPoints.Clear();
|
||||
gridPoints.Add(fromRect.center);
|
||||
gridPoints.AddRange(reroutePoints);
|
||||
gridPoints.Add(toRect.center);
|
||||
DrawNoodle(noodleColor, gridPoints);
|
||||
DrawNoodle(noodleGradient, noodlePath, noodleStroke, noodleThickness, gridPoints);
|
||||
|
||||
// Loop through reroute points again and draw the points
|
||||
for (int i = 0; i < reroutePoints.Count; i++) {
|
||||
@ -235,7 +326,7 @@ namespace XNodeEditor {
|
||||
GUI.DrawTexture(rect, NodeEditorResources.dotOuter);
|
||||
}
|
||||
|
||||
GUI.color = noodleColor;
|
||||
GUI.color = portColor;
|
||||
GUI.DrawTexture(rect, NodeEditorResources.dot);
|
||||
if (rect.Overlaps(selectionBox)) selection.Add(rerouteRef);
|
||||
if (rect.Contains(mousePos)) hoveredReroute = rerouteRef;
|
||||
@ -281,6 +372,8 @@ namespace XNodeEditor {
|
||||
//Save guiColor so we can revert it
|
||||
Color guiColor = GUI.color;
|
||||
|
||||
List<XNode.NodePort> removeEntries = new List<XNode.NodePort>();
|
||||
|
||||
if (e.type == EventType.Layout) culledNodes = new List<XNode.Node>();
|
||||
for (int n = 0; n < graph.nodes.Count; n++) {
|
||||
// Skip null nodes. The user could be in the process of renaming scripts, so removing them at this point is not advisable.
|
||||
@ -298,7 +391,10 @@ namespace XNodeEditor {
|
||||
} else if (culledNodes.Contains(node)) continue;
|
||||
|
||||
if (e.type == EventType.Repaint) {
|
||||
_portConnectionPoints = _portConnectionPoints.Where(x => x.Key.node != node).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||
removeEntries.Clear();
|
||||
foreach (var kvp in _portConnectionPoints)
|
||||
if (kvp.Key.node == node) removeEntries.Add(kvp.Key);
|
||||
foreach (var k in removeEntries) _portConnectionPoints.Remove(k);
|
||||
}
|
||||
|
||||
NodeEditor nodeEditor = NodeEditor.GetEditor(node, this);
|
||||
|
||||
@ -41,9 +41,7 @@ namespace XNodeEditor {
|
||||
else {
|
||||
Rect rect = new Rect();
|
||||
|
||||
float spacePadding = 0;
|
||||
SpaceAttribute spaceAttribute;
|
||||
if (NodeEditorUtilities.GetCachedAttrib(port.node.GetType(), property.name, out spaceAttribute)) spacePadding = spaceAttribute.height;
|
||||
List<PropertyAttribute> propertyAttributes = NodeEditorUtilities.GetCachedPropertyAttribs(port.node.GetType(), property.name);
|
||||
|
||||
// If property is an input, display a regular property field and put a port handle on the left side
|
||||
if (port.direction == XNode.NodePort.IO.Input) {
|
||||
@ -56,13 +54,24 @@ namespace XNodeEditor {
|
||||
showBacking = inputAttribute.backingValue;
|
||||
}
|
||||
|
||||
//Call GUILayout.Space if Space attribute is set and we are NOT drawing a PropertyField
|
||||
bool useLayoutSpace = dynamicPortList ||
|
||||
bool usePropertyAttributes = dynamicPortList ||
|
||||
showBacking == XNode.Node.ShowBackingValue.Never ||
|
||||
(showBacking == XNode.Node.ShowBackingValue.Unconnected && port.IsConnected);
|
||||
if (spacePadding > 0 && useLayoutSpace) {
|
||||
GUILayout.Space(spacePadding);
|
||||
spacePadding = 0;
|
||||
|
||||
float spacePadding = 0;
|
||||
foreach (var attr in propertyAttributes) {
|
||||
if (attr is SpaceAttribute) {
|
||||
if (usePropertyAttributes) GUILayout.Space((attr as SpaceAttribute).height);
|
||||
else spacePadding += (attr as SpaceAttribute).height;
|
||||
} else if (attr is HeaderAttribute) {
|
||||
if (usePropertyAttributes) {
|
||||
//GUI Values are from https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/ScriptAttributeGUI/Implementations/DecoratorDrawers.cs
|
||||
Rect position = GUILayoutUtility.GetRect(0, (EditorGUIUtility.singleLineHeight * 1.5f) - EditorGUIUtility.standardVerticalSpacing); //Layout adds standardVerticalSpacing after rect so we subtract it.
|
||||
position.yMin += EditorGUIUtility.singleLineHeight * 0.5f;
|
||||
position = EditorGUI.IndentedRect(position);
|
||||
GUI.Label(position, (attr as HeaderAttribute).header, EditorStyles.boldLabel);
|
||||
} else spacePadding += EditorGUIUtility.singleLineHeight * 1.5f;
|
||||
}
|
||||
}
|
||||
|
||||
if (dynamicPortList) {
|
||||
@ -101,13 +110,24 @@ namespace XNodeEditor {
|
||||
showBacking = outputAttribute.backingValue;
|
||||
}
|
||||
|
||||
//Call GUILayout.Space if Space attribute is set and we are NOT drawing a PropertyField
|
||||
bool useLayoutSpace = dynamicPortList ||
|
||||
bool usePropertyAttributes = dynamicPortList ||
|
||||
showBacking == XNode.Node.ShowBackingValue.Never ||
|
||||
(showBacking == XNode.Node.ShowBackingValue.Unconnected && port.IsConnected);
|
||||
if (spacePadding > 0 && useLayoutSpace) {
|
||||
GUILayout.Space(spacePadding);
|
||||
spacePadding = 0;
|
||||
|
||||
float spacePadding = 0;
|
||||
foreach (var attr in propertyAttributes) {
|
||||
if (attr is SpaceAttribute) {
|
||||
if (usePropertyAttributes) GUILayout.Space((attr as SpaceAttribute).height);
|
||||
else spacePadding += (attr as SpaceAttribute).height;
|
||||
} else if (attr is HeaderAttribute) {
|
||||
if (usePropertyAttributes) {
|
||||
//GUI Values are from https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/ScriptAttributeGUI/Implementations/DecoratorDrawers.cs
|
||||
Rect position = GUILayoutUtility.GetRect(0, (EditorGUIUtility.singleLineHeight * 1.5f) - EditorGUIUtility.standardVerticalSpacing); //Layout adds standardVerticalSpacing after rect so we subtract it.
|
||||
position.yMin += EditorGUIUtility.singleLineHeight * 0.5f;
|
||||
position = EditorGUI.IndentedRect(position);
|
||||
GUI.Label(position, (attr as HeaderAttribute).header, EditorStyles.boldLabel);
|
||||
} else spacePadding += EditorGUIUtility.singleLineHeight * 1.5f;
|
||||
}
|
||||
}
|
||||
|
||||
if (dynamicPortList) {
|
||||
|
||||
@ -2,10 +2,13 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace XNodeEditor {
|
||||
public enum NoodlePath { Curvy, Straight, Angled }
|
||||
public enum NoodleStroke { Full, Dashed }
|
||||
|
||||
public static class NodeEditorPreferences {
|
||||
public enum NoodleType { Curve, Line, Angled }
|
||||
|
||||
/// <summary> The last editor we checked. This should be the one we modify </summary>
|
||||
private static XNodeEditor.NodeGraphEditor lastEditor;
|
||||
@ -37,7 +40,8 @@ namespace XNodeEditor {
|
||||
public bool portTooltips = true;
|
||||
[SerializeField] private string typeColorsData = "";
|
||||
[NonSerialized] public Dictionary<string, Color> typeColors = new Dictionary<string, Color>();
|
||||
public NoodleType noodleType = NoodleType.Curve;
|
||||
[FormerlySerializedAs("noodleType")] public NoodlePath noodlePath = NoodlePath.Curvy;
|
||||
public NoodleStroke noodleStroke = NoodleStroke.Full;
|
||||
|
||||
private Texture2D _gridTexture;
|
||||
public Texture2D gridTexture {
|
||||
@ -77,6 +81,8 @@ namespace XNodeEditor {
|
||||
|
||||
/// <summary> Get settings of current active editor </summary>
|
||||
public static Settings GetSettings() {
|
||||
if (XNodeEditor.NodeEditorWindow.current == null) return new Settings();
|
||||
|
||||
if (lastEditor != XNodeEditor.NodeEditorWindow.current.graphEditor) {
|
||||
object[] attribs = XNodeEditor.NodeEditorWindow.current.graphEditor.GetType().GetCustomAttributes(typeof(XNodeEditor.NodeGraphEditor.CustomNodeGraphEditorAttribute), true);
|
||||
if (attribs.Length == 1) {
|
||||
@ -106,7 +112,7 @@ namespace XNodeEditor {
|
||||
private static void PreferencesGUI() {
|
||||
VerifyLoaded();
|
||||
Settings settings = NodeEditorPreferences.settings[lastKey];
|
||||
|
||||
|
||||
if (GUILayout.Button(new GUIContent("Documentation", "https://github.com/Siccity/xNode/wiki"), GUILayout.Width(100))) Application.OpenURL("https://github.com/Siccity/xNode/wiki");
|
||||
EditorGUILayout.Space();
|
||||
|
||||
@ -151,7 +157,8 @@ namespace XNodeEditor {
|
||||
//Label
|
||||
EditorGUILayout.LabelField("Node", EditorStyles.boldLabel);
|
||||
settings.highlightColor = EditorGUILayout.ColorField("Selection", settings.highlightColor);
|
||||
settings.noodleType = (NoodleType) EditorGUILayout.EnumPopup("Noodle type", (Enum) settings.noodleType);
|
||||
settings.noodlePath = (NoodlePath) EditorGUILayout.EnumPopup("Noodle path", (Enum) settings.noodlePath);
|
||||
settings.noodleStroke = (NoodleStroke) EditorGUILayout.EnumPopup("Noodle stroke", (Enum) settings.noodleStroke);
|
||||
settings.portTooltips = EditorGUILayout.Toggle("Port Tooltips", settings.portTooltips);
|
||||
settings.dragToCreate = EditorGUILayout.Toggle(new GUIContent("Drag to Create", "Drag a port connection anywhere on the grid to create and connect a node"), settings.dragToCreate);
|
||||
if (GUI.changed) {
|
||||
|
||||
@ -18,6 +18,9 @@ namespace XNodeEditor {
|
||||
/// Saves Attribute from Type+Field for faster lookup. Resets on recompiles.
|
||||
private static Dictionary<Type, Dictionary<string, Dictionary<Type, Attribute>>> typeAttributes = new Dictionary<Type, Dictionary<string, Dictionary<Type, Attribute>>>();
|
||||
|
||||
/// Saves ordered PropertyAttribute from Type+Field for faster lookup. Resets on recompiles.
|
||||
private static Dictionary<Type, Dictionary<string, List<PropertyAttribute>>> typeOrderedPropertyAttributes = new Dictionary<Type, Dictionary<string, List<PropertyAttribute>>>();
|
||||
|
||||
public static bool GetAttrib<T>(Type classType, out T attribOut) where T : Attribute {
|
||||
object[] attribs = classType.GetCustomAttributes(typeof(T), false);
|
||||
return GetAttrib(attribs, out attribOut);
|
||||
@ -84,6 +87,24 @@ namespace XNodeEditor {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static List<PropertyAttribute> GetCachedPropertyAttribs(Type classType, string fieldName) {
|
||||
Dictionary<string, List<PropertyAttribute>> typeFields;
|
||||
if (!typeOrderedPropertyAttributes.TryGetValue(classType, out typeFields)) {
|
||||
typeFields = new Dictionary<string, List<PropertyAttribute>>();
|
||||
typeOrderedPropertyAttributes.Add(classType, typeFields);
|
||||
}
|
||||
|
||||
List<PropertyAttribute> typeAttributes;
|
||||
if (!typeFields.TryGetValue(fieldName, out typeAttributes)) {
|
||||
FieldInfo field = classType.GetFieldInfo(fieldName);
|
||||
object[] attribs = field.GetCustomAttributes(typeof(PropertyAttribute), true);
|
||||
typeAttributes = attribs.Cast<PropertyAttribute>().Reverse().ToList(); //Unity draws them in reverse
|
||||
typeFields.Add(fieldName, typeAttributes);
|
||||
}
|
||||
|
||||
return typeAttributes;
|
||||
}
|
||||
|
||||
public static bool IsMac() {
|
||||
#if UNITY_2017_1_OR_NEWER
|
||||
return SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX;
|
||||
|
||||
@ -63,11 +63,50 @@ namespace XNodeEditor {
|
||||
menu.AddCustomContextMenuItems(target);
|
||||
}
|
||||
|
||||
/// <summary> Returned color is used to color noodles </summary>
|
||||
public virtual Color GetNoodleColor(XNode.NodePort output, XNode.NodePort input) {
|
||||
Color col = GetTypeColor(output.ValueType);
|
||||
if (window.hoveredPort == output || window.hoveredPort == input) return Color.Lerp(col, Color.white, 0.8f);
|
||||
return col;
|
||||
/// <summary> Returned gradient is used to color noodles </summary>
|
||||
/// <param name="output"> The output this noodle comes from. Never null. </param>
|
||||
/// <param name="input"> The output this noodle comes from. Can be null if we are dragging the noodle. </param>
|
||||
public virtual Gradient GetNoodleGradient(XNode.NodePort output, XNode.NodePort input) {
|
||||
Gradient grad = new Gradient();
|
||||
|
||||
// If dragging the noodle, draw solid, slightly transparent
|
||||
if (input == null) {
|
||||
Color a = GetTypeColor(output.ValueType);
|
||||
grad.SetKeys(
|
||||
new GradientColorKey[] { new GradientColorKey(a, 0f) },
|
||||
new GradientAlphaKey[] { new GradientAlphaKey(0.6f, 0f) }
|
||||
);
|
||||
}
|
||||
// If normal, draw gradient fading from one input color to the other
|
||||
else {
|
||||
Color a = GetTypeColor(output.ValueType);
|
||||
Color b = GetTypeColor(input.ValueType);
|
||||
// If any port is hovered, tint white
|
||||
if (window.hoveredPort == output || window.hoveredPort == input) {
|
||||
a = Color.Lerp(a, Color.white, 0.8f);
|
||||
b = Color.Lerp(b, Color.white, 0.8f);
|
||||
}
|
||||
grad.SetKeys(
|
||||
new GradientColorKey[] { new GradientColorKey(a, 0f), new GradientColorKey(b, 1f) },
|
||||
new GradientAlphaKey[] { new GradientAlphaKey(1f, 0f), new GradientAlphaKey(1f, 1f) }
|
||||
);
|
||||
}
|
||||
return grad;
|
||||
}
|
||||
|
||||
/// <summary> Returned float is used for noodle thickness </summary>
|
||||
/// <param name="output"> The output this noodle comes from. Never null. </param>
|
||||
/// <param name="input"> The output this noodle comes from. Can be null if we are dragging the noodle. </param>
|
||||
public virtual float GetNoodleThickness(XNode.NodePort output, XNode.NodePort input) {
|
||||
return 5f;
|
||||
}
|
||||
|
||||
public virtual NoodlePath GetNoodlePath(XNode.NodePort output, XNode.NodePort input) {
|
||||
return NodeEditorPreferences.GetSettings().noodlePath;
|
||||
}
|
||||
|
||||
public virtual NoodleStroke GetNoodleStroke(XNode.NodePort output, XNode.NodePort input) {
|
||||
return NodeEditorPreferences.GetSettings().noodleStroke;
|
||||
}
|
||||
|
||||
/// <summary> Returned color is used to color ports </summary>
|
||||
@ -94,12 +133,14 @@ namespace XNodeEditor {
|
||||
|
||||
/// <summary> Deal with objects dropped into the graph through DragAndDrop </summary>
|
||||
public virtual void OnDropObjects(UnityEngine.Object[] objects) {
|
||||
Debug.Log("No OnDropItems override defined for " + GetType());
|
||||
Debug.Log("No OnDropObjects override defined for " + GetType());
|
||||
}
|
||||
|
||||
/// <summary> Create a node and save it in the graph asset </summary>
|
||||
public virtual XNode.Node CreateNode(Type type, Vector2 position) {
|
||||
Undo.RecordObject(target, "Create Node");
|
||||
XNode.Node node = target.AddNode(type);
|
||||
Undo.RegisterCreatedObjectUndo(node, "Create Node");
|
||||
node.position = position;
|
||||
if (node.name == null || node.name.Trim() == "") node.name = NodeEditorUtilities.NodeDefaultName(type);
|
||||
AssetDatabase.AddObjectToAsset(node, target);
|
||||
@ -110,7 +151,9 @@ namespace XNodeEditor {
|
||||
|
||||
/// <summary> Creates a copy of the original node in the graph </summary>
|
||||
public XNode.Node CopyNode(XNode.Node original) {
|
||||
Undo.RecordObject(target, "Duplicate Node");
|
||||
XNode.Node node = target.CopyNode(original);
|
||||
Undo.RegisterCreatedObjectUndo(node, "Duplicate Node");
|
||||
node.name = original.name;
|
||||
AssetDatabase.AddObjectToAsset(node, target);
|
||||
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
|
||||
@ -119,8 +162,13 @@ namespace XNodeEditor {
|
||||
|
||||
/// <summary> Safely remove a node and all its connections. </summary>
|
||||
public virtual void RemoveNode(XNode.Node node) {
|
||||
Undo.RecordObject(node, "Delete Node");
|
||||
Undo.RecordObject(target, "Delete Node");
|
||||
foreach (var port in node.Ports)
|
||||
foreach (var conn in port.GetConnections())
|
||||
Undo.RecordObject(conn.node, "Delete Node");
|
||||
target.RemoveNode(node);
|
||||
UnityEngine.Object.DestroyImmediate(node, true);
|
||||
Undo.DestroyObjectImmediate(node);
|
||||
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
|
||||
}
|
||||
|
||||
|
||||
@ -45,10 +45,12 @@ namespace XNode {
|
||||
public enum TypeConstraint {
|
||||
/// <summary> Allow all types of input</summary>
|
||||
None,
|
||||
/// <summary> Allow similar and inherited types </summary>
|
||||
/// <summary> Allow connections where input value type is assignable from output value type (eg. ScriptableObject --> Object)</summary>
|
||||
Inherited,
|
||||
/// <summary> Allow only similar types </summary>
|
||||
Strict,
|
||||
/// <summary> Allow connections where output value type is assignable from input value type (eg. Object --> ScriptableObject)</summary>
|
||||
InheritedInverse,
|
||||
}
|
||||
|
||||
#region Obsolete
|
||||
|
||||
@ -199,6 +199,10 @@ namespace XNode {
|
||||
if (port == this) { Debug.LogWarning("Cannot connect port to self."); return; }
|
||||
if (IsConnectedTo(port)) { Debug.LogWarning("Port already connected. "); return; }
|
||||
if (direction == port.direction) { Debug.LogWarning("Cannot connect two " + (direction == IO.Input ? "input" : "output") + " connections"); return; }
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.Undo.RecordObject(node, "Connect Port");
|
||||
UnityEditor.Undo.RecordObject(port.node, "Connect Port");
|
||||
#endif
|
||||
if (port.connectionType == Node.ConnectionType.Override && port.ConnectionCount != 0) { port.ClearConnections(); }
|
||||
if (connectionType == Node.ConnectionType.Override && ConnectionCount != 0) { ClearConnections(); }
|
||||
connections.Add(new PortConnection(port));
|
||||
@ -259,9 +263,11 @@ namespace XNode {
|
||||
// Check input type constraints
|
||||
if (input.typeConstraint == XNode.Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) return false;
|
||||
if (input.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false;
|
||||
if (input.typeConstraint == XNode.Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) return false;
|
||||
// Check output type constraints
|
||||
if (output.typeConstraint == XNode.Node.TypeConstraint.Inherited && !output.ValueType.IsAssignableFrom(input.ValueType)) return false;
|
||||
if (output.typeConstraint == XNode.Node.TypeConstraint.Strict && output.ValueType != input.ValueType) return false;
|
||||
if (output.typeConstraint == XNode.Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) return false;
|
||||
if (output.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false;
|
||||
if (output.typeConstraint == XNode.Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) return false;
|
||||
// Success
|
||||
return true;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user