summaryrefslogtreecommitdiff
path: root/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions
diff options
context:
space:
mode:
authorchai <chaifix@163.com>2022-06-28 09:40:37 +0800
committerchai <chaifix@163.com>2022-06-28 09:40:37 +0800
commit49b25e755b70ec412feaaf0b898d6f7e09d2bea6 (patch)
tree3c5f4260f30d1c2d7196db93153700d7ddec3157 /Other/NodeEditorExamples/Assets/UNEB/Editor/Actions
parentc92269331692feca2c276649f6c4ee8911f1f859 (diff)
+node example
Diffstat (limited to 'Other/NodeEditorExamples/Assets/UNEB/Editor/Actions')
-rw-r--r--Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/ActionBase.cs23
-rw-r--r--Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/ActionBase.cs.meta12
-rw-r--r--Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/ActionManager.cs169
-rw-r--r--Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/ActionManager.cs.meta12
-rw-r--r--Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/ActionTriggerSystem.cs456
-rw-r--r--Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/ActionTriggerSystem.cs.meta12
-rw-r--r--Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/CreateConnection.cs106
-rw-r--r--Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/CreateConnection.cs.meta12
-rw-r--r--Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/CreateNodeAction.cs55
-rw-r--r--Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/CreateNodeAction.cs.meta12
-rw-r--r--Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/DeleteNodeAction.cs128
-rw-r--r--Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/DeleteNodeAction.cs.meta12
-rw-r--r--Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/DragNode.cs42
-rw-r--r--Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/DragNode.cs.meta12
-rw-r--r--Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/MultiStageAction.cs18
-rw-r--r--Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/MultiStageAction.cs.meta12
-rw-r--r--Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/RemoveConnection.cs32
-rw-r--r--Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/RemoveConnection.cs.meta12
-rw-r--r--Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/UndoableAction.cs12
-rw-r--r--Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/UndoableAction.cs.meta12
20 files changed, 1161 insertions, 0 deletions
diff --git a/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/ActionBase.cs b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/ActionBase.cs
new file mode 100644
index 00000000..426c8a50
--- /dev/null
+++ b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/ActionBase.cs
@@ -0,0 +1,23 @@
+
+namespace UNEB
+{
+ public abstract class ActionBase
+ {
+
+ public ActionManager manager;
+
+ /// <summary>
+ /// Can be used to check if the action is a valid state for furthur execution.
+ /// For example, we only want to run delete node if a node is selected for deletion.
+ /// </summary>
+ /// <returns></returns>
+ public virtual bool Init() { return true; }
+
+ public abstract void Do();
+
+ /// <summary>
+ /// Called when the action is removed from the undo/redo buffers.
+ /// </summary>
+ public virtual void OnDestroy() { }
+ }
+} \ No newline at end of file
diff --git a/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/ActionBase.cs.meta b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/ActionBase.cs.meta
new file mode 100644
index 00000000..eb9d005c
--- /dev/null
+++ b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/ActionBase.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 5dfa0b77744c2bc4eb973c5114f5de79
+timeCreated: 1501781587
+licenseType: Free
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/ActionManager.cs b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/ActionManager.cs
new file mode 100644
index 00000000..b0450b46
--- /dev/null
+++ b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/ActionManager.cs
@@ -0,0 +1,169 @@
+
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+using UNEB.Utility;
+
+namespace UNEB
+{
+ /// <summary>
+ /// Handles execution of actions, undo, and redo.
+ /// </summary>
+ public class ActionManager
+ {
+ private NodeEditorWindow _window;
+
+ private FiniteStack<UndoableAction> _undoStack;
+ private Stack<UndoableAction> _redoStack;
+
+ // Caches the current multi-stage action that is currently executing.
+ private MultiStageAction _activeMultiAction = null;
+
+ public event Action OnUndo;
+ public event Action OnRedo;
+
+ public ActionManager(NodeEditorWindow w)
+ {
+ _undoStack = new FiniteStack<UndoableAction>(100);
+ _redoStack = new Stack<UndoableAction>();
+
+ _window = w;
+
+ // Makes sure that the action cleans up after itself
+ // when it is removed from the undo buffer.
+ _undoStack.OnRemoveBottomItem += (action) =>
+ {
+ action.OnDestroy();
+ };
+ }
+
+ public void Update()
+ {
+ if (IsRunningAction) {
+ _activeMultiAction.Do();
+ }
+ }
+
+ public bool IsRunningAction
+ {
+ get { return _activeMultiAction != null; }
+ }
+
+ /// <summary>
+ /// Runs an action and stores it in the undo stack.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ public void RunUndoableAction<T>() where T : UndoableAction, new()
+ {
+ T action = new T();
+ action.manager = this;
+
+ if (action.Init()) {
+
+ clearRedoStack();
+ _undoStack.Push(action);
+ action.Do();
+ }
+ }
+
+ /// <summary>
+ /// Starts a multi stage action but does not record it in the undo stack.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ public void StartMultiStageAction<T>() where T : MultiStageAction, new()
+ {
+ // Only run 1 multi-action at a time.
+ if (_activeMultiAction != null) {
+ return;
+ }
+
+ T action = new T();
+ action.manager = this;
+
+ if (action.Init()) {
+ _activeMultiAction = action;
+ _activeMultiAction.OnActionStart();
+ }
+ }
+
+ /// <summary>
+ /// Records the multi-stage action in the undo stack.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="action"></param>
+ public void FinishMultiStageAction()
+ {
+ if (_activeMultiAction == null) {
+ return;
+ }
+
+ // We check if the action ended properly so it can be stored in undo.
+ if (_activeMultiAction.OnActionEnd()) {
+
+ clearRedoStack();
+ _undoStack.Push(_activeMultiAction);
+ }
+
+ // There is no longer an active multi-stage action.
+ _activeMultiAction = null;
+ }
+
+ public void UndoAction()
+ {
+ if (_undoStack.Count != 0) {
+
+ var action = _undoStack.Pop();
+ _redoStack.Push(action);
+
+ action.Undo();
+
+ if (OnUndo != null)
+ OnUndo();
+ }
+ }
+
+ public void RedoAction()
+ {
+ if (_redoStack.Count != 0) {
+
+ var action = _redoStack.Pop();
+ _undoStack.Push(action);
+
+ action.Redo();
+
+ if (OnRedo != null)
+ OnRedo();
+ }
+ }
+
+ public void Reset()
+ {
+ _activeMultiAction = null;
+ clearUndoStack();
+ clearRedoStack();
+ }
+
+ private void clearRedoStack()
+ {
+ foreach (var action in _redoStack) {
+ action.OnDestroy();
+ }
+
+ _redoStack.Clear();
+ }
+
+ private void clearUndoStack()
+ {
+ foreach (var action in _undoStack) {
+ action.OnDestroy();
+ }
+
+ _undoStack.Clear();
+ }
+
+ public NodeEditorWindow window
+ {
+ get { return _window; }
+ }
+ }
+} \ No newline at end of file
diff --git a/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/ActionManager.cs.meta b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/ActionManager.cs.meta
new file mode 100644
index 00000000..3cc64c36
--- /dev/null
+++ b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/ActionManager.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 62fe01f31afcc5a4ab446003a7360cb3
+timeCreated: 1501892528
+licenseType: Free
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/ActionTriggerSystem.cs b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/ActionTriggerSystem.cs
new file mode 100644
index 00000000..d5089754
--- /dev/null
+++ b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/ActionTriggerSystem.cs
@@ -0,0 +1,456 @@
+
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEditor;
+using UNEB.Utility;
+using System.Reflection;
+using System.Linq;
+
+namespace UNEB
+{
+ public class ActionTriggerSystem
+ {
+ public List<TriggerMapping> triggers;
+
+ /// <summary>
+ /// Passive triggers do not interrupt the next possible trigger.
+ /// </summary>
+ public List<TriggerMapping> passiveTriggers;
+
+ private ActionManager _manager;
+ private TriggerMapping _focus;
+
+ public ActionTriggerSystem(ActionManager m)
+ {
+ _manager = m;
+
+ triggers = new List<TriggerMapping>();
+ passiveTriggers = new List<TriggerMapping>();
+
+ setupStandardTriggers();
+ }
+
+ public void Update()
+ {
+ foreach (TriggerMapping tm in triggers) {
+ if (tm.AllTriggersSatisfied()) {
+ tm.action();
+ return;
+ }
+ }
+
+ foreach (TriggerMapping tm in passiveTriggers) {
+ if (tm.AllTriggersSatisfied()) {
+ tm.action();
+ }
+ }
+
+ // Block all key inputs from passing through the Unity Editor
+ if (Event.current.isKey)
+ Event.current.Use();
+ }
+
+ private void setupStandardTriggers()
+ {
+ setupImmediateTriggers();
+ setupContextTriggers();
+ setupMultiStageTriggers();
+ }
+
+ private void setupImmediateTriggers()
+ {
+ var panInput = Create<InputTrigger>().Mouse(EventType.MouseDrag, InputTrigger.Button.Wheel);
+ panInput.action = () =>
+ {
+ window.editor.Pan(Event.current.delta);
+ window.Repaint();
+ };
+
+ var zoomInput = Create<InputTrigger>().Key(EventType.ScrollWheel, KeyCode.None, false, false);
+ zoomInput.action = () =>
+ {
+ window.editor.Zoom(Event.current.delta.y);
+ window.Repaint();
+ };
+
+ var selectSingle = Create<InputTrigger>().Mouse(EventType.MouseDown, InputTrigger.Button.Left);
+ selectSingle.action = () =>
+ {
+ bool bResult = window.editor.OnMouseOverNode(onSingleSelected);
+
+ // If the canvas is clicked then remove focus of GUI elements.
+ if (!bResult) {
+ GUI.FocusControl(null);
+ window.Repaint();
+ }
+ };
+
+ var undoInput = Create<InputTrigger>().Key(EventType.KeyDown, KeyCode.Z, true, false);
+ undoInput.action = _manager.UndoAction;
+
+ var redoInput = Create<InputTrigger>().Key(EventType.KeyDown, KeyCode.Y, true, false);
+ redoInput.action = _manager.RedoAction;
+
+ var recordClick = Create<InputTrigger>().EventOnly(EventType.MouseDown);
+ recordClick.action = () => { window.state.lastClickedPosition = window.editor.MousePosition(); };
+
+ var homeView = Create<InputTrigger>().Key(EventType.KeyDown, KeyCode.F, false, false);
+ homeView.action = window.editor.HomeView;
+ }
+
+ private void setupContextTriggers()
+ {
+ setupNodeCreateMenu();
+
+ Pair<string, Action>[] nodeContext =
+ {
+ ContextItem("Copy Node", () => { Debug.Log("Not Implemented"); }),
+ ContextItem("Delete Node", _manager.RunUndoableAction<DeleteNodeAction>)
+ };
+
+ var nodeTrigger = Create<ContextTrigger>().Build(nodeContext).EventOnly(EventType.ContextClick);
+ nodeTrigger.triggers.Add(isMouseOverNode);
+ nodeTrigger.triggers.Add(isGraphValid);
+ }
+
+ private void setupNodeCreateMenu()
+ {
+ //Get all classes deriving from Node via reflection
+ Type derivedType = typeof(Node);
+ Assembly assembly = Assembly.GetAssembly(derivedType);
+
+ List<Type> nodeTypes = assembly
+ .GetTypes()
+ .Where(t =>
+ t != derivedType &&
+ derivedType.IsAssignableFrom(t)
+ ).ToList();
+
+ //Populate canvasContext with entries for all node types
+ var canvasContext = new Pair<string, Action>[nodeTypes.Count];
+
+ for (int i = 0; i < nodeTypes.Count; i++) {
+
+ Type nodeType = nodeTypes[i];
+ Action createNode = () =>
+ {
+ _manager.window.state.typeToCreate = nodeType;
+ _manager.RunUndoableAction<CreateNodeAction>();
+ };
+
+ string name = ObjectNames.NicifyVariableName(nodeType.Name);
+ canvasContext[i] = ContextItem(name, createNode);
+ }
+
+ var canvasTrigger = Create<ContextTrigger>().Build(canvasContext).EventOnly(EventType.ContextClick);
+ canvasTrigger.triggers.Add(isMouseOverCanvas);
+ canvasTrigger.triggers.Add(isGraphValid);
+ }
+
+ private void setupMultiStageTriggers()
+ {
+ setupNodeDrag();
+ setupNodeConnection();
+ }
+
+ private void setupNodeDrag()
+ {
+ var endDragInput = Create<InputTrigger>(false, true).Mouse(EventType.MouseUp, InputTrigger.Button.Left);
+ var runningDragInput = Create<InputTrigger>(false, true).EventOnly(EventType.MouseDrag);
+ var startDragInput = Create<InputTrigger>(false, true).Mouse(EventType.MouseDown, InputTrigger.Button.Left);
+
+ startDragInput.triggers.Add(isMouseOverNode);
+ startDragInput.action = _manager.StartMultiStageAction<DragNode>;
+
+ endDragInput.action = _manager.FinishMultiStageAction;
+
+ runningDragInput.action = _manager.Update;
+ runningDragInput.action += window.Repaint;
+
+ new MultiStageInputTrigger(startDragInput, endDragInput, runningDragInput);
+ }
+
+ private void setupNodeConnection()
+ {
+ var endConnInput = Create<InputTrigger>(false, true).Mouse(EventType.MouseUp, InputTrigger.Button.Left);
+ var runningConnInput = Create<InputTrigger>(false, true).EventOnly(EventType.MouseDrag);
+ var startConnInput = Create<InputTrigger>(false, true).Mouse(EventType.MouseDown, InputTrigger.Button.Left);
+
+ Func<bool> knobCondition = () => { return isMouseOverOutput() || isMouseOverInputStartConn(); };
+
+ startConnInput.triggers.Add(knobCondition);
+ startConnInput.triggers.Add(isOutputSelected);
+
+ startConnInput.action = _manager.StartMultiStageAction<CreateConnection>;
+
+ endConnInput.action = _manager.FinishMultiStageAction;
+ endConnInput.action += window.Repaint;
+
+ runningConnInput.action = _manager.Update;
+ runningConnInput.action += window.Repaint;
+
+ new MultiStageInputTrigger(startConnInput, endConnInput, runningConnInput);
+ }
+
+ /// <summary>
+ /// Create a trigger mapping and store it in the triggers list.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <returns></returns>
+ public T Create<T>(bool isPassive = false, bool pushToFront = false) where T : TriggerMapping, new()
+ {
+ T mapping = new T();
+
+ if (isPassive) {
+
+ if (pushToFront && passiveTriggers.Count > 0) passiveTriggers.Insert(0, mapping);
+ else passiveTriggers.Add(mapping);
+ }
+
+ else {
+
+ if (pushToFront && triggers.Count > 0) triggers.Insert(0, mapping);
+ else triggers.Add(mapping);
+ }
+
+ return mapping;
+ }
+
+ private void onSingleSelected(Node node)
+ {
+ _manager.window.state.selectedNode = node;
+ _manager.window.graph.PushToEnd(node);
+
+ Selection.activeObject = node;
+ }
+
+ private void onOutputKnobSelected(NodeOutput output)
+ {
+ _manager.window.state.selectedOutput = output;
+ }
+
+ private bool isMouseOverNode()
+ {
+ return window.editor.OnMouseOverNode(onSingleSelected);
+ }
+
+ private bool isMouseOverCanvas()
+ {
+ return !isMouseOverNode();
+ }
+
+ private bool isMouseOverOutput()
+ {
+ return window.editor.OnMouseOverOutput(onOutputKnobSelected);
+ }
+
+ private bool isMouseOverInputStartConn()
+ {
+ Action<NodeInput> startConnFromInput = (NodeInput input) =>
+ {
+ window.state.selectedOutput = input.Outputs[0];
+
+ // Detach this input if we are starting a connection action from the input.
+ if (window.state.selectedOutput != null) {
+
+ window.state.selectedInput = input;
+ _manager.RunUndoableAction<RemoveConnection>();
+ }
+ };
+
+ return window.editor.OnMouseOverInput(startConnFromInput);
+ }
+
+ private bool isOutputSelected()
+ {
+ return window.state.selectedOutput != null;
+ }
+
+ private NodeEditorWindow window
+ {
+ get { return _manager.window; }
+ }
+
+ private bool isGraphValid()
+ {
+ return window.graph != null;
+ }
+
+ public Pair<string, Action> ContextItem(string label, Action a)
+ {
+ return new Pair<string, Action>(label, a);
+ }
+
+ /// <summary>
+ /// Maps a conditional trigger with an action.
+ /// </summary>
+ public class TriggerMapping
+ {
+ public List<Func<bool>> triggers = new List<Func<bool>>();
+ public Action action;
+
+ protected TriggerMapping() { }
+
+ public TriggerMapping(Func<bool> trigger, Action action)
+ {
+ ;
+ triggers.Add(trigger);
+ this.action = action;
+ }
+
+ public bool AllTriggersSatisfied()
+ {
+ foreach (var t in triggers) {
+ if (!t()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+
+ /// <summary>
+ /// Special trigger that uses input as a conditional.
+ /// </summary>
+ public class InputTrigger : TriggerMapping
+ {
+ public enum Button
+ {
+ Left = 0,
+ Right = 1,
+ Wheel = 2
+ }
+
+ private EventType t;
+ private KeyCode k;
+ private int button;
+ private bool bIsShift, bIsCtrl;
+
+ /// <summary>
+ /// Initialize the input mapping with a key trigger.
+ /// </summary>
+ /// <param name="type"></param>
+ /// <param name="key"></param>
+ /// <param name="bShift"></param>
+ /// <param name="bCtrl"></param>
+ /// <returns></returns>
+ public InputTrigger Key(EventType type, KeyCode key, bool bShift, bool bCtrl)
+ {
+ t = type;
+ k = key;
+
+ bIsShift = bShift;
+ bIsCtrl = bCtrl;
+
+ Func<bool> trigger = () =>
+ {
+ var e = Event.current;
+
+ return
+ e.type == t &&
+ e.keyCode == k &&
+ e.shift == bIsShift &&
+ e.control == bIsCtrl;
+ };
+
+ triggers.Add(trigger);
+ return this;
+ }
+
+ /// <summary>
+ /// Initialize the input mapping with a mouse button tirgger.
+ /// </summary>
+ /// <param name="type"></param>
+ /// <param name="mButton"></param>
+ /// <returns></returns>
+ public InputTrigger Mouse(EventType type, Button mButton)
+ {
+ t = type;
+ button = (int)mButton;
+
+ Func<bool> trigger = () =>
+ {
+ var e = Event.current;
+
+ return
+ e.type == t &&
+ e.button == button;
+ };
+
+ triggers.Add(trigger);
+ return this;
+ }
+
+ /// <summary>
+ /// Initializes the input mapping with the event.
+ /// </summary>
+ /// <param name="type"></param>
+ /// <returns></returns>
+ public InputTrigger EventOnly(EventType type)
+ {
+ t = type;
+ Func<bool> trigger = () => { return Event.current.type == t; };
+
+ triggers.Add(trigger);
+ return this;
+ }
+ }
+
+ /// <summary>
+ /// Special trigger that uses context menus to execute other actions.
+ /// </summary>
+ public class ContextTrigger : InputTrigger
+ {
+ private GenericMenu menu;
+
+ public ContextTrigger()
+ {
+ menu = new GenericMenu();
+ action = menu.ShowAsContext;
+ }
+
+ public ContextTrigger Build(params Pair<string, Action>[] contents)
+ {
+ foreach (var content in contents) {
+
+ string label = content.item1;
+ Action action = content.item2;
+
+ menu.AddItem(new GUIContent(label), false, () => { action(); });
+ }
+
+ return this;
+ }
+ }
+
+ public class MultiStageInputTrigger
+ {
+ private InputTrigger _startTrigger, _endTrigger, _runningTrigger;
+
+ private bool _bStarted = false;
+
+ public MultiStageInputTrigger(InputTrigger start, InputTrigger end, InputTrigger running)
+ {
+ start.action += () => { _bStarted = true; };
+ start.triggers.Add(hasNotStarted);
+
+ end.triggers.Add(HasStarted);
+ end.action += () => { _bStarted = false; };
+
+ running.triggers.Add(HasStarted);
+ }
+
+ public bool HasStarted()
+ {
+ return _bStarted;
+ }
+
+ private bool hasNotStarted()
+ {
+ return !_bStarted;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/ActionTriggerSystem.cs.meta b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/ActionTriggerSystem.cs.meta
new file mode 100644
index 00000000..6d66a0d4
--- /dev/null
+++ b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/ActionTriggerSystem.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 2b8fd837eb8876e4fa8c9b94d8b977f0
+timeCreated: 1501897267
+licenseType: Free
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/CreateConnection.cs b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/CreateConnection.cs
new file mode 100644
index 00000000..09d3e521
--- /dev/null
+++ b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/CreateConnection.cs
@@ -0,0 +1,106 @@
+
+using System.Linq;
+using System.Collections.Generic;
+
+using UnityEngine;
+using UnityEditor;
+
+namespace UNEB
+{
+ public class CreateConnection : MultiStageAction
+ {
+ private NodeInput _input;
+ private NodeOutput _output;
+
+ // The output of the old node it was connected to.
+ private NodeOutput _oldConnectedOutput;
+
+ // Old inputs of the node.
+ private List<NodeInput> _oldConnectedInputs;
+
+ public override void Do()
+ {
+ manager.window.state.selectedOutput = _output;
+ }
+
+ public override void Undo()
+ {
+ _output.Remove(_input);
+ reconnectOldConnections();
+ }
+
+ public override void Redo()
+ {
+ disconnectOldConnections();
+ _output.Add(_input);
+ }
+
+ private void reconnectOldConnections()
+ {
+ // Re-connect old connections
+ if (_oldConnectedOutput != null) {
+ _oldConnectedOutput.Add(_input);
+ }
+
+ if (_oldConnectedInputs != null) {
+ foreach (var input in _oldConnectedInputs) {
+ _output.Add(input);
+ }
+ }
+ }
+
+ private void disconnectOldConnections()
+ {
+ // Remove old connections
+ if (_oldConnectedOutput != null) {
+ _oldConnectedOutput.Remove(_input);
+ }
+
+ if (_oldConnectedInputs != null) {
+ _output.RemoveAll();
+ }
+ }
+
+ public override void OnActionStart()
+ {
+ _output = manager.window.state.selectedOutput;
+ }
+
+ public override bool OnActionEnd()
+ {
+ manager.window.state.selectedOutput = null;
+ manager.window.editor.OnMouseOverInput((input) => { _input = input; });
+
+ // Make the connection.
+ if (_input != null && _output.CanConnectInput(_input)) {
+
+ if (!_output.bCanHaveMultipleConnections)
+ {
+ _output.RemoveAll();
+ }
+
+ if (!_input.bCanHaveMultipleConnections) {
+ cacheOldConnections();
+ disconnectOldConnections();
+ }
+
+ return _output.Add(_input);
+ }
+
+ return false;
+ }
+
+ private void cacheOldConnections()
+ {
+ // Check if the receiving node was already connected.
+ if (_input != null && _input.HasOutputConnected()) {
+ _oldConnectedOutput = _input.Outputs[0];
+ }
+
+ // Check if the origin node already had inputs
+ if (!_output.bCanHaveMultipleConnections && _output.InputCount > 0) {
+ _oldConnectedInputs = _output.Inputs.ToList();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/CreateConnection.cs.meta b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/CreateConnection.cs.meta
new file mode 100644
index 00000000..5f6468b1
--- /dev/null
+++ b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/CreateConnection.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 89cf0324fa09d7d4b82a3308a2172117
+timeCreated: 1501807840
+licenseType: Free
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/CreateNodeAction.cs b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/CreateNodeAction.cs
new file mode 100644
index 00000000..522fa0cb
--- /dev/null
+++ b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/CreateNodeAction.cs
@@ -0,0 +1,55 @@
+
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace UNEB
+{
+ public class CreateNodeAction : UndoableAction
+ {
+ private NodeGraph _graph;
+ private Node _nodeCreated;
+
+ // The node referenced can only be destroyed if the
+ // create action has been undone.
+ private bool _bCanDeleteNode = false;
+
+ public override bool Init()
+ {
+ System.Type t = manager.window.state.typeToCreate;
+ return t != null && typeof(Node).IsAssignableFrom(t);
+ }
+
+ public override void Do()
+ {
+ _graph = manager.window.graph;
+
+ var state = manager.window.state;
+
+ _nodeCreated = SaveManager.CreateNode(state.typeToCreate, _graph);
+ _nodeCreated.bodyRect.position = manager.window.state.lastClickedPosition;
+
+ // Done with this type creation.
+ state.typeToCreate = null;
+ }
+
+ public override void Undo()
+ {
+ _graph.Remove(_nodeCreated);
+ _bCanDeleteNode = true;
+ }
+
+ public override void Redo()
+ {
+ _graph.Add(_nodeCreated);
+ _bCanDeleteNode = false;
+ }
+
+ public override void OnDestroy()
+ {
+ if (_bCanDeleteNode && _nodeCreated) {
+ ScriptableObject.DestroyImmediate(_nodeCreated, true);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/CreateNodeAction.cs.meta b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/CreateNodeAction.cs.meta
new file mode 100644
index 00000000..06f04a85
--- /dev/null
+++ b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/CreateNodeAction.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: f1c2e7f15404be3468c08ac729975c13
+timeCreated: 1501781600
+licenseType: Free
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/DeleteNodeAction.cs b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/DeleteNodeAction.cs
new file mode 100644
index 00000000..2e2e6672
--- /dev/null
+++ b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/DeleteNodeAction.cs
@@ -0,0 +1,128 @@
+
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using UnityEngine;
+using UNEB.Utility;
+
+namespace UNEB
+{
+ // Each input can have many outputs
+ using InputToOutputPair = Pair<NodeInput, List<NodeOutput>>;
+
+ // Each output can have many inputs
+ using OutputToInputsPair = Pair<NodeOutput, List<NodeInput>>;
+
+ public class DeleteNodeAction : UndoableAction
+ {
+ private NodeGraph _graph;
+ private Node _nodeRemoved = null;
+
+ private List<InputToOutputPair> _oldConnectedOutputs;
+ private List<OutputToInputsPair> _oldConnectedInputs;
+
+ // The node referenced can only be destroyed if the
+ // delete action has been done or redone.
+ private bool _bCanDeleteNode = false;
+
+ public DeleteNodeAction()
+ {
+
+ _oldConnectedOutputs = new List<InputToOutputPair>();
+ _oldConnectedInputs = new List<OutputToInputsPair>();
+ }
+
+ public override bool Init()
+ {
+ return manager.window.state.selectedNode != null;
+ }
+
+ public override void Do()
+ {
+ _graph = manager.window.graph;
+ _nodeRemoved = manager.window.state.selectedNode;
+ _graph.Remove(_nodeRemoved);
+
+ // Remember all the old outputs the inputs were connected to.
+ foreach (var input in _nodeRemoved.Inputs) {
+
+ if (input.HasOutputConnected()) {
+ _oldConnectedOutputs.Add(new InputToOutputPair(input, input.Outputs.ToList()));
+ }
+ }
+
+ // Remember all the old input connections that the outputs were connected to.
+ foreach (var output in _nodeRemoved.Outputs) {
+
+ if (output.InputCount != 0) {
+ _oldConnectedInputs.Add(new OutputToInputsPair(output, output.Inputs.ToList()));
+ }
+ }
+
+ disconnectOldConnections();
+
+ _bCanDeleteNode = true;
+ }
+
+ public override void Undo()
+ {
+ _graph.Add(_nodeRemoved);
+ reconnectOldConnections();
+
+ _bCanDeleteNode = false;
+ }
+
+ public override void Redo()
+ {
+ _graph.Remove(_nodeRemoved);
+ disconnectOldConnections();
+
+ _bCanDeleteNode = true;
+ }
+
+ private void disconnectOldConnections()
+ {
+ // For all the outputs for this node, remove all the connected inputs.
+ foreach (var output in _nodeRemoved.Outputs) {
+ output.RemoveAll();
+ }
+
+ // For all the inputs for this node, remove all the connected outputs.
+ foreach (var input in _nodeRemoved.Inputs) {
+ input.RemoveAll();
+ }
+ }
+
+ private void reconnectOldConnections()
+ {
+ // For all the remembered inputs (of this node) to output pairs, reconnect.
+ foreach (InputToOutputPair inOutPair in _oldConnectedOutputs) {
+
+ NodeInput input = inOutPair.item1;
+ List<NodeOutput> outputs = inOutPair.item2;
+
+ foreach (var output in outputs) {
+ output.Add(input);
+ }
+ }
+
+ // For all the remembered outputs (of this node) to inputs, reconnect.
+ foreach (OutputToInputsPair outInsPair in _oldConnectedInputs) {
+
+ NodeOutput output = outInsPair.item1;
+ List<NodeInput> inputs = outInsPair.item2;
+
+ foreach (var input in inputs) {
+ output.Add(input);
+ }
+ }
+ }
+
+ public override void OnDestroy()
+ {
+ if (_bCanDeleteNode && _nodeRemoved) {
+ ScriptableObject.DestroyImmediate(_nodeRemoved, true);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/DeleteNodeAction.cs.meta b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/DeleteNodeAction.cs.meta
new file mode 100644
index 00000000..4a62ff87
--- /dev/null
+++ b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/DeleteNodeAction.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 5653c134c34e88b4a971e196d0f42586
+timeCreated: 1501781608
+licenseType: Free
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/DragNode.cs b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/DragNode.cs
new file mode 100644
index 00000000..9e4d89d0
--- /dev/null
+++ b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/DragNode.cs
@@ -0,0 +1,42 @@
+
+using UnityEngine;
+
+namespace UNEB
+{
+ public class DragNode : MultiStageAction
+ {
+ private Node _draggingNode;
+
+ private Vector2 _startDragPos, _endDragPos;
+
+ public const float dragSpeed = 1f;
+
+ public override void Undo()
+ {
+ _draggingNode.bodyRect.position = _startDragPos;
+ }
+
+ public override void Redo()
+ {
+ _draggingNode.bodyRect.position = _endDragPos;
+ }
+
+ public override void Do()
+ {
+ NodeEditor editor = manager.window.editor;
+ _draggingNode.bodyRect.position += Event.current.delta * editor.ZoomScale * dragSpeed;
+ }
+
+ public override void OnActionStart()
+ {
+ _draggingNode = manager.window.state.selectedNode;
+ _startDragPos = _draggingNode.bodyRect.position;
+ }
+
+ public override bool OnActionEnd()
+ {
+ _endDragPos = _draggingNode.bodyRect.position;
+ return true;
+ }
+ }
+} \ No newline at end of file
diff --git a/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/DragNode.cs.meta b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/DragNode.cs.meta
new file mode 100644
index 00000000..efbc5101
--- /dev/null
+++ b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/DragNode.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 2075b47cce2d17147a02ec2483f38698
+timeCreated: 1501792802
+licenseType: Free
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/MultiStageAction.cs b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/MultiStageAction.cs
new file mode 100644
index 00000000..b7fd41fb
--- /dev/null
+++ b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/MultiStageAction.cs
@@ -0,0 +1,18 @@
+
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace UNEB
+{
+ public abstract class MultiStageAction : UndoableAction
+ {
+
+ public abstract void OnActionStart();
+
+ /// <summary>
+ /// Returns true if the action completed succesfully.
+ /// </summary>
+ /// <returns></returns>
+ public abstract bool OnActionEnd();
+ }
+} \ No newline at end of file
diff --git a/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/MultiStageAction.cs.meta b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/MultiStageAction.cs.meta
new file mode 100644
index 00000000..bc4a1081
--- /dev/null
+++ b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/MultiStageAction.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 211fa78df12a1d440a6aa57ca343d5ab
+timeCreated: 1501892778
+licenseType: Free
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/RemoveConnection.cs b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/RemoveConnection.cs
new file mode 100644
index 00000000..341e9f63
--- /dev/null
+++ b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/RemoveConnection.cs
@@ -0,0 +1,32 @@
+
+using UnityEngine;
+
+namespace UNEB
+{
+ public class RemoveConnection : UndoableAction
+ {
+
+ private NodeOutput _output;
+ private NodeInput _input;
+
+ public override void Do()
+ {
+ _input = manager.window.state.selectedInput;
+ _output = _input.Outputs[0];
+
+ _output.Remove(_input);
+
+ manager.window.state.selectedInput = null;
+ }
+
+ public override void Undo()
+ {
+ _output.Add(_input);
+ }
+
+ public override void Redo()
+ {
+ _output.Remove(_input);
+ }
+ }
+} \ No newline at end of file
diff --git a/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/RemoveConnection.cs.meta b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/RemoveConnection.cs.meta
new file mode 100644
index 00000000..fec8868b
--- /dev/null
+++ b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/RemoveConnection.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: d1b449f51ea4820498a2b38021e549d7
+timeCreated: 1501913312
+licenseType: Free
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/UndoableAction.cs b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/UndoableAction.cs
new file mode 100644
index 00000000..53857a9f
--- /dev/null
+++ b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/UndoableAction.cs
@@ -0,0 +1,12 @@
+
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace UNEB
+{
+ public abstract class UndoableAction : ActionBase
+ {
+ public abstract void Undo();
+ public abstract void Redo();
+ }
+} \ No newline at end of file
diff --git a/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/UndoableAction.cs.meta b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/UndoableAction.cs.meta
new file mode 100644
index 00000000..b6e7512b
--- /dev/null
+++ b/Other/NodeEditorExamples/Assets/UNEB/Editor/Actions/UndoableAction.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 72c02675816f81d4c9d15134303127b4
+timeCreated: 1501892769
+licenseType: Free
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant: