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 triggers; /// /// Passive triggers do not interrupt the next possible trigger. /// public List passiveTriggers; private ActionManager _manager; private TriggerMapping _focus; public ActionTriggerSystem(ActionManager m) { _manager = m; triggers = new List(); passiveTriggers = new List(); 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().Mouse(EventType.MouseDrag, InputTrigger.Button.Wheel); panInput.action = () => { window.editor.Pan(Event.current.delta); window.Repaint(); }; var zoomInput = Create().Key(EventType.ScrollWheel, KeyCode.None, false, false); zoomInput.action = () => { window.editor.Zoom(Event.current.delta.y); window.Repaint(); }; var selectSingle = Create().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().Key(EventType.KeyDown, KeyCode.Z, true, false); undoInput.action = _manager.UndoAction; var redoInput = Create().Key(EventType.KeyDown, KeyCode.Y, true, false); redoInput.action = _manager.RedoAction; var recordClick = Create().EventOnly(EventType.MouseDown); recordClick.action = () => { window.state.lastClickedPosition = window.editor.MousePosition(); }; var homeView = Create().Key(EventType.KeyDown, KeyCode.F, false, false); homeView.action = window.editor.HomeView; } private void setupContextTriggers() { setupNodeCreateMenu(); Pair[] nodeContext = { ContextItem("Copy Node", () => { Debug.Log("Not Implemented"); }), ContextItem("Delete Node", _manager.RunUndoableAction) }; var nodeTrigger = Create().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 nodeTypes = assembly .GetTypes() .Where(t => t != derivedType && derivedType.IsAssignableFrom(t) ).ToList(); //Populate canvasContext with entries for all node types var canvasContext = new Pair[nodeTypes.Count]; for (int i = 0; i < nodeTypes.Count; i++) { Type nodeType = nodeTypes[i]; Action createNode = () => { _manager.window.state.typeToCreate = nodeType; _manager.RunUndoableAction(); }; string name = ObjectNames.NicifyVariableName(nodeType.Name); canvasContext[i] = ContextItem(name, createNode); } var canvasTrigger = Create().Build(canvasContext).EventOnly(EventType.ContextClick); canvasTrigger.triggers.Add(isMouseOverCanvas); canvasTrigger.triggers.Add(isGraphValid); } private void setupMultiStageTriggers() { setupNodeDrag(); setupNodeConnection(); } private void setupNodeDrag() { var endDragInput = Create(false, true).Mouse(EventType.MouseUp, InputTrigger.Button.Left); var runningDragInput = Create(false, true).EventOnly(EventType.MouseDrag); var startDragInput = Create(false, true).Mouse(EventType.MouseDown, InputTrigger.Button.Left); startDragInput.triggers.Add(isMouseOverNode); startDragInput.action = _manager.StartMultiStageAction; endDragInput.action = _manager.FinishMultiStageAction; runningDragInput.action = _manager.Update; runningDragInput.action += window.Repaint; new MultiStageInputTrigger(startDragInput, endDragInput, runningDragInput); } private void setupNodeConnection() { var endConnInput = Create(false, true).Mouse(EventType.MouseUp, InputTrigger.Button.Left); var runningConnInput = Create(false, true).EventOnly(EventType.MouseDrag); var startConnInput = Create(false, true).Mouse(EventType.MouseDown, InputTrigger.Button.Left); Func knobCondition = () => { return isMouseOverOutput() || isMouseOverInputStartConn(); }; startConnInput.triggers.Add(knobCondition); startConnInput.triggers.Add(isOutputSelected); startConnInput.action = _manager.StartMultiStageAction; endConnInput.action = _manager.FinishMultiStageAction; endConnInput.action += window.Repaint; runningConnInput.action = _manager.Update; runningConnInput.action += window.Repaint; new MultiStageInputTrigger(startConnInput, endConnInput, runningConnInput); } /// /// Create a trigger mapping and store it in the triggers list. /// /// /// public T Create(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 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(); } }; 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 ContextItem(string label, Action a) { return new Pair(label, a); } /// /// Maps a conditional trigger with an action. /// public class TriggerMapping { public List> triggers = new List>(); public Action action; protected TriggerMapping() { } public TriggerMapping(Func trigger, Action action) { ; triggers.Add(trigger); this.action = action; } public bool AllTriggersSatisfied() { foreach (var t in triggers) { if (!t()) { return false; } } return true; } } /// /// Special trigger that uses input as a conditional. /// 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; /// /// Initialize the input mapping with a key trigger. /// /// /// /// /// /// public InputTrigger Key(EventType type, KeyCode key, bool bShift, bool bCtrl) { t = type; k = key; bIsShift = bShift; bIsCtrl = bCtrl; Func trigger = () => { var e = Event.current; return e.type == t && e.keyCode == k && e.shift == bIsShift && e.control == bIsCtrl; }; triggers.Add(trigger); return this; } /// /// Initialize the input mapping with a mouse button tirgger. /// /// /// /// public InputTrigger Mouse(EventType type, Button mButton) { t = type; button = (int)mButton; Func trigger = () => { var e = Event.current; return e.type == t && e.button == button; }; triggers.Add(trigger); return this; } /// /// Initializes the input mapping with the event. /// /// /// public InputTrigger EventOnly(EventType type) { t = type; Func trigger = () => { return Event.current.type == t; }; triggers.Add(trigger); return this; } } /// /// Special trigger that uses context menus to execute other actions. /// public class ContextTrigger : InputTrigger { private GenericMenu menu; public ContextTrigger() { menu = new GenericMenu(); action = menu.ShowAsContext; } public ContextTrigger Build(params Pair[] 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; } } } }