1
0
mirror of https://github.com/Siccity/xNode.git synced 2026-02-04 22:34:54 +08:00

Added multinode selection

This commit is contained in:
Thor Brigsted 2018-01-22 18:06:50 +01:00
parent fcbd48b0be
commit 5d113de554
6 changed files with 228 additions and 48 deletions

View File

@ -6,22 +6,20 @@ namespace XNodeEditor {
public partial class NodeEditorWindow { public partial class NodeEditorWindow {
public static bool isPanning { get; private set; } public static bool isPanning { get; private set; }
public static Vector2 dragOffset; public static Vector2[] dragOffset;
private bool IsDraggingNode { get { return draggedNode != null; } } //private bool IsDraggingNode { get { return draggedNode != null; } }
private bool IsDraggingPort { get { return draggedOutput != null; } } private bool IsDraggingPort { get { return draggedOutput != null; } }
private bool IsHoveringPort { get { return hoveredPort != null; } } private bool IsHoveringPort { get { return hoveredPort != null; } }
private bool IsHoveringNode { get { return hoveredNode != null; } } private bool IsHoveringNode { get { return hoveredNode != null; } }
private bool HasSelectedNode { get { return selectedNode != null; } } public bool CanDragNodeHeader { get; private set; }
public bool DidDragNodeHeader { get; private set; }
private XNode.Node hoveredNode = null; private XNode.Node hoveredNode = null;
[NonSerialized] private XNode.Node selectedNode = null; //[NonSerialized] private XNode.Node draggedNode = null;
[NonSerialized] private XNode.Node draggedNode = null;
[NonSerialized] private XNode.NodePort hoveredPort = null; [NonSerialized] private XNode.NodePort hoveredPort = null;
[NonSerialized] private XNode.NodePort draggedOutput = null; [NonSerialized] private XNode.NodePort draggedOutput = null;
[NonSerialized] private XNode.NodePort draggedOutputTarget = null; [NonSerialized] private XNode.NodePort draggedOutputTarget = null;
private Rect nodeRects; private Rect nodeRects;
public void Controls() { public void Controls() {
@ -45,12 +43,18 @@ namespace XNodeEditor {
draggedOutputTarget = null; draggedOutputTarget = null;
} }
Repaint(); Repaint();
} else if (IsDraggingNode) { } else if (CanDragNodeHeader) {
draggedNode.position = WindowToGridPosition(e.mousePosition) + dragOffset; for (int i = 0; i < Selection.objects.Length; i++) {
if (NodeEditorPreferences.gridSnap) { if (Selection.objects[i] is XNode.Node) {
draggedNode.position.x = (Mathf.Round((draggedNode.position.x + 8) / 16) * 16) - 8; XNode.Node node = Selection.objects[i] as XNode.Node;
draggedNode.position.y = (Mathf.Round((draggedNode.position.y + 8) / 16) * 16) - 8; node.position = WindowToGridPosition(e.mousePosition) + dragOffset[i];
if (NodeEditorPreferences.gridSnap) {
node.position.x = (Mathf.Round((node.position.x + 8) / 16) * 16) - 8;
node.position.y = (Mathf.Round((node.position.y + 8) / 16) * 16) - 8;
}
}
} }
DidDragNodeHeader = true;
Repaint(); Repaint();
} }
} else if (e.button == 1 || e.button == 2) { } else if (e.button == 1 || e.button == 2) {
@ -66,7 +70,11 @@ namespace XNodeEditor {
case EventType.MouseDown: case EventType.MouseDown:
Repaint(); Repaint();
if (e.button == 0) { if (e.button == 0) {
SelectNode(hoveredNode);
if (hoveredNode == null) Selection.activeObject = null;
else if (!Selection.Contains(hoveredNode)) SelectNode(hoveredNode, e.control || e.shift);
else if (e.control || e.shift) DeselectNode(hoveredNode);
if (IsHoveringPort) { if (IsHoveringPort) {
if (hoveredPort.IsOutput) { if (hoveredPort.IsOutput) {
draggedOutput = hoveredPort; draggedOutput = hoveredPort;
@ -82,8 +90,15 @@ namespace XNodeEditor {
} }
} }
} else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) { } else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) {
draggedNode = hoveredNode; DidDragNodeHeader = false;
dragOffset = hoveredNode.position - WindowToGridPosition(e.mousePosition); CanDragNodeHeader = true;
dragOffset = new Vector2[Selection.objects.Length];
for (int i = 0; i < dragOffset.Length; i++) {
if (Selection.objects[i] is XNode.Node) {
XNode.Node node = Selection.objects[i] as XNode.Node;
dragOffset[i] = node.position - WindowToGridPosition(e.mousePosition);
}
}
} }
} }
break; break;
@ -104,16 +119,22 @@ namespace XNodeEditor {
EditorUtility.SetDirty(graph); EditorUtility.SetDirty(graph);
Repaint(); Repaint();
AssetDatabase.SaveAssets(); AssetDatabase.SaveAssets();
} else if (IsDraggingNode) { } else if (CanDragNodeHeader) {
draggedNode = null; CanDragNodeHeader = false;
AssetDatabase.SaveAssets(); AssetDatabase.SaveAssets();
} else if (GUIUtility.hotControl != 0) { } else if (GUIUtility.hotControl != 0) {
AssetDatabase.SaveAssets(); AssetDatabase.SaveAssets();
} }
if (IsHoveringNode && !DidDragNodeHeader && !e.control) {
SelectNode(hoveredNode, false);
Repaint();
}
} else if (e.button == 1) { } else if (e.button == 1) {
if (!isPanning) { if (!isPanning) {
if (IsHoveringNode && IsHoveringTitle(hoveredNode)) { if (IsHoveringNode && IsHoveringTitle(hoveredNode)) {
ShowNodeContextMenu(hoveredNode); if (!Selection.Contains(hoveredNode)) SelectNode(hoveredNode,false);
ShowNodeContextMenu();
} else if (!IsHoveringNode) { } else if (!IsHoveringNode) {
ShowGraphContextMenu(); ShowGraphContextMenu();
} }
@ -121,6 +142,11 @@ namespace XNodeEditor {
isPanning = false; isPanning = false;
} }
break; break;
case EventType.KeyDown:
if (e.keyCode == KeyCode.Delete) RemoveSelectedNodes();
else if (e.keyCode == KeyCode.D && e.control) DublicateSelectedNodes();
Repaint();
break;
} }
} }
@ -136,6 +162,31 @@ namespace XNodeEditor {
Repaint(); Repaint();
} }
/// <summary> Remove nodes in the graph in Selection.objects</summary>
public void RemoveSelectedNodes() {
foreach (UnityEngine.Object item in Selection.objects) {
if (item is XNode.Node) {
XNode.Node node = item as XNode.Node;
graph.RemoveNode(node);
}
}
}
// <summary> Dublicate selected nodes and select the dublicates
public void DublicateSelectedNodes() {
UnityEngine.Object[] newNodes = new UnityEngine.Object[Selection.objects.Length];
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;
if (node.graph != graph) continue; // ignore nodes selected in another graph
XNode.Node n = graph.CopyNode(node);
n.position = node.position + new Vector2(30, 30);
newNodes[i] = n;
}
}
Selection.objects = newNodes;
}
/// <summary> Draw a connection as we are dragging it </summary> /// <summary> Draw a connection as we are dragging it </summary>
public void DrawDraggedConnection() { public void DrawDraggedConnection() {
if (IsDraggingPort) { if (IsDraggingPort) {

View File

@ -6,7 +6,8 @@ using UnityEngine;
namespace XNodeEditor { namespace XNodeEditor {
/// <summary> Contains GUI methods </summary> /// <summary> Contains GUI methods </summary>
public partial class NodeEditorWindow { public partial class NodeEditorWindow {
NodeGraphEditor currentGraphEditor; private NodeGraphEditor currentGraphEditor;
private List<UnityEngine.Object> selectionCache;
private void OnGUI() { private void OnGUI() {
Event e = Event.current; Event e = Event.current;
@ -74,23 +75,30 @@ namespace XNodeEditor {
return GUILayout.Button(name, EditorStyles.toolbarDropDown, GUILayout.Width(width)); return GUILayout.Button(name, EditorStyles.toolbarDropDown, GUILayout.Width(width));
} }
/// <summary> Show right-click context menu for a node </summary> /// <summary> Show right-click context menu for selected nodes </summary>
public void ShowNodeContextMenu(XNode.Node node) { public void ShowNodeContextMenu() {
GenericMenu contextMenu = new GenericMenu(); GenericMenu contextMenu = new GenericMenu();
contextMenu.AddItem(new GUIContent("Move To Top"), false, () => { // If only one node is selected
int index; if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) {
while ((index = graph.nodes.IndexOf(node)) != graph.nodes.Count - 1) { XNode.Node node = Selection.activeObject as XNode.Node;
graph.nodes[index] = graph.nodes[index + 1]; contextMenu.AddItem(new GUIContent("Move To Top"), false, () => {
graph.nodes[index + 1] = node; int index;
} while ((index = graph.nodes.IndexOf(node)) != graph.nodes.Count - 1) {
}); graph.nodes[index] = graph.nodes[index + 1];
contextMenu.AddItem(new GUIContent("Duplicate"), false, () => { graph.nodes[index + 1] = node;
XNode.Node n = graph.CopyNode(node); }
n.position = node.position + new Vector2(30, 30); });
}); }
contextMenu.AddItem(new GUIContent("Remove"), false, () => graph.RemoveNode(node));
contextMenu.AddItem(new GUIContent("Duplicate"), false, DublicateSelectedNodes);
contextMenu.AddItem(new GUIContent("Remove"), false, RemoveSelectedNodes);
// If only one node is selected
if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) {
XNode.Node node = Selection.activeObject as XNode.Node;
AddCustomContextMenuItems(contextMenu, node);
}
AddCustomContextMenuItems(contextMenu, node);
contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
} }
@ -168,17 +176,20 @@ namespace XNodeEditor {
private void DrawNodes() { private void DrawNodes() {
Event e = Event.current; Event e = Event.current;
if (e.type == EventType.Layout) {
selectionCache = new List<UnityEngine.Object>(Selection.objects);
}
if (e.type == EventType.Repaint) { if (e.type == EventType.Repaint) {
portConnectionPoints.Clear(); portConnectionPoints.Clear();
nodeWidths.Clear(); nodeWidths.Clear();
} }
//Selected node is hashed before and after node GUI to detect changes //Active node is hashed before and after node GUI to detect changes
int nodeHash = 0; int nodeHash = 0;
System.Reflection.MethodInfo onValidate = null; System.Reflection.MethodInfo onValidate = null;
if (selectedNode != null) { if (Selection.activeObject != null && Selection.activeObject is XNode.Node) {
onValidate = selectedNode.GetType().GetMethod("OnValidate"); onValidate = Selection.activeObject.GetType().GetMethod("OnValidate");
if (onValidate != null) nodeHash = selectedNode.GetHashCode(); if (onValidate != null) nodeHash = Selection.activeObject.GetHashCode();
} }
BeginZoomed(position, zoom); BeginZoomed(position, zoom);
@ -206,9 +217,23 @@ namespace XNodeEditor {
GUILayout.BeginArea(new Rect(nodePos, new Vector2(nodeEditor.GetWidth(), 4000))); GUILayout.BeginArea(new Rect(nodePos, new Vector2(nodeEditor.GetWidth(), 4000)));
GUIStyle style = NodeEditorResources.styles.nodeBody; bool selected = selectionCache.Contains(graph.nodes[n]);
GUI.color = nodeEditor.GetTint();
GUILayout.BeginVertical(new GUIStyle(style)); if (selected) {
GUIStyle style = new GUIStyle(NodeEditorResources.styles.nodeBody);
GUIStyle highlightStyle = new GUIStyle(NodeEditorResources.styles.nodeHighlight);
highlightStyle.padding = style.padding;
style.padding = new RectOffset();
GUI.color = nodeEditor.GetTint();
GUILayout.BeginVertical(new GUIStyle(style));
GUI.color = Color.white;
GUILayout.BeginVertical(new GUIStyle(highlightStyle));
} else {
GUIStyle style = NodeEditorResources.styles.nodeBody;
GUI.color = nodeEditor.GetTint();
GUILayout.BeginVertical(new GUIStyle(style));
}
GUI.color = guiColor; GUI.color = guiColor;
EditorGUI.BeginChangeCheck(); EditorGUI.BeginChangeCheck();
@ -235,6 +260,7 @@ namespace XNodeEditor {
} }
GUILayout.EndVertical(); GUILayout.EndVertical();
if (selected) GUILayout.EndVertical();
if (e.type != EventType.Layout) { if (e.type != EventType.Layout) {
//Check if we are hovering this node //Check if we are hovering this node
@ -267,8 +293,8 @@ namespace XNodeEditor {
//If a change in hash is detected in the selected node, call OnValidate method. //If a change in hash is detected in the selected node, call OnValidate method.
//This is done through reflection because OnValidate is only relevant in editor, //This is done through reflection because OnValidate is only relevant in editor,
//and thus, the code should not be included in build. //and thus, the code should not be included in build.
if (selectedNode != null) { if (nodeHash != 0) {
if (onValidate != null && nodeHash != selectedNode.GetHashCode()) onValidate.Invoke(selectedNode, null); if (onValidate != null && nodeHash != Selection.activeObject.GetHashCode()) onValidate.Invoke(Selection.activeObject, null);
} }
} }

View File

@ -9,13 +9,15 @@ namespace XNodeEditor {
private static Texture2D _dotOuter; private static Texture2D _dotOuter;
public static Texture2D nodeBody { get { return _nodeBody != null ? _nodeBody : _nodeBody = Resources.Load<Texture2D>("xnode_node"); } } public static Texture2D nodeBody { get { return _nodeBody != null ? _nodeBody : _nodeBody = Resources.Load<Texture2D>("xnode_node"); } }
private static Texture2D _nodeBody; private static Texture2D _nodeBody;
public static Texture2D nodeHighlight { get { return _nodeHighlight != null ? _nodeHighlight : _nodeHighlight = Resources.Load<Texture2D>("xnode_node_highlight"); } }
private static Texture2D _nodeHighlight;
// Styles // Styles
public static Styles styles { get { return _styles != null ? _styles : _styles = new Styles(); } } public static Styles styles { get { return _styles != null ? _styles : _styles = new Styles(); } }
public static Styles _styles = null; public static Styles _styles = null;
public class Styles { public class Styles {
public GUIStyle inputPort, outputPort, nodeHeader, nodeBody, tooltip; public GUIStyle inputPort, outputPort, nodeHeader, nodeBody, tooltip, nodeHighlight;
public Styles() { public Styles() {
GUIStyle baseStyle = new GUIStyle("Label"); GUIStyle baseStyle = new GUIStyle("Label");
@ -39,6 +41,10 @@ namespace XNodeEditor {
nodeBody.border = new RectOffset(32, 32, 32, 32); nodeBody.border = new RectOffset(32, 32, 32, 32);
nodeBody.padding = new RectOffset(16, 16, 4, 16); nodeBody.padding = new RectOffset(16, 16, 4, 16);
nodeHighlight = new GUIStyle();
nodeHighlight.normal.background = NodeEditorResources.nodeHighlight;
nodeHighlight.border = new RectOffset(32, 32, 32, 32);
tooltip = new GUIStyle("helpBox"); tooltip = new GUIStyle("helpBox");
tooltip.alignment = TextAnchor.MiddleCenter; tooltip.alignment = TextAnchor.MiddleCenter;
} }
@ -50,8 +56,8 @@ namespace XNodeEditor {
for (int y = 0; y < 64; y++) { for (int y = 0; y < 64; y++) {
for (int x = 0; x < 64; x++) { for (int x = 0; x < 64; x++) {
Color col = bg; Color col = bg;
if (y % 16 == 0 || x % 16 == 0) col = Color.Lerp(line,bg,0.65f); if (y % 16 == 0 || x % 16 == 0) col = Color.Lerp(line, bg, 0.65f);
if (y == 63 || x == 63) col = Color.Lerp(line,bg,0.35f); if (y == 63 || x == 63) col = Color.Lerp(line, bg, 0.35f);
cols[(y * 64) + x] = col; cols[(y * 64) + x] = col;
} }
} }

View File

@ -77,8 +77,18 @@ namespace XNodeEditor {
return new Vector2(xOffset, yOffset); return new Vector2(xOffset, yOffset);
} }
public void SelectNode(XNode.Node node) { public void SelectNode(XNode.Node node, bool add) {
selectedNode = node; if (add) {
List<Object> selection = new List<Object>(Selection.objects);
selection.Add(node);
Selection.objects = selection.ToArray();
} else Selection.activeObject = node;
}
public void DeselectNode(XNode.Node node) {
List<Object> selection = new List<Object>(Selection.objects);
selection.Remove(node);
Selection.objects = selection.ToArray();
} }
[OnOpenAsset(0)] [OnOpenAsset(0)]

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,87 @@
fileFormatVersion: 2
guid: 2ab2b92d7e1771b47bba0a46a6f0f6d5
timeCreated: 1516610730
licenseType: Free
TextureImporter:
fileIDToRecycleName: {}
externalObjects: {}
serializedVersion: 4
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 0
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -1
wrapU: 1
wrapV: 1
wrapW: -1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
- buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant: