496 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			496 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System;
 | |
| using System.Collections.Generic;
 | |
| using System.Linq;
 | |
| using UnityEditor;
 | |
| using UnityEngine;
 | |
| using UnityObject = UnityEngine.Object;
 | |
| 
 | |
| namespace Unity.VisualScripting
 | |
| {
 | |
|     [Canvas(typeof(FlowGraph))]
 | |
|     public sealed class FlowCanvas : VisualScriptingCanvas<FlowGraph>
 | |
|     {
 | |
|         public FlowCanvas(FlowGraph graph) : base(graph) { }
 | |
| 
 | |
| 
 | |
|         #region Clipboard
 | |
| 
 | |
|         public override void ShrinkCopyGroup(HashSet<IGraphElement> copyGroup)
 | |
|         {
 | |
|             copyGroup.RemoveWhere(element =>
 | |
|             {
 | |
|                 if (element is IUnitConnection)
 | |
|                 {
 | |
|                     var connection = (IUnitConnection)element;
 | |
| 
 | |
|                     if (!copyGroup.Contains(connection.source.unit) ||
 | |
|                         !copyGroup.Contains(connection.destination.unit))
 | |
|                     {
 | |
|                         return true;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 return false;
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         #endregion
 | |
| 
 | |
| 
 | |
|         #region Window
 | |
| 
 | |
|         public override void OnToolbarGUI()
 | |
|         {
 | |
|             showRelations = GUILayout.Toggle(showRelations, "Relations", LudiqStyles.toolbarButton);
 | |
| 
 | |
|             EditorGUI.BeginChangeCheck();
 | |
| 
 | |
|             BoltFlow.Configuration.showConnectionValues = GUILayout.Toggle(BoltFlow.Configuration.showConnectionValues, "Values", LudiqStyles.toolbarButton);
 | |
| 
 | |
|             BoltCore.Configuration.dimInactiveNodes = GUILayout.Toggle(BoltCore.Configuration.dimInactiveNodes, "Dim", LudiqStyles.toolbarButton);
 | |
| 
 | |
|             if (EditorGUI.EndChangeCheck())
 | |
|             {
 | |
|                 BoltFlow.Configuration.Save();
 | |
| 
 | |
|                 BoltCore.Configuration.Save();
 | |
|             }
 | |
| 
 | |
|             base.OnToolbarGUI();
 | |
|         }
 | |
| 
 | |
|         #endregion
 | |
| 
 | |
| 
 | |
|         #region View
 | |
| 
 | |
|         protected override bool shouldEdgePan => base.shouldEdgePan || isCreatingConnection;
 | |
| 
 | |
|         public const float inspectorZoomThreshold = 0.7f;
 | |
| 
 | |
|         #endregion
 | |
| 
 | |
| 
 | |
|         #region Lifecycle
 | |
| 
 | |
|         public override void Close()
 | |
|         {
 | |
|             base.Close();
 | |
| 
 | |
|             CancelConnection();
 | |
|         }
 | |
| 
 | |
|         protected override void HandleHighPriorityInput()
 | |
|         {
 | |
|             if (isCreatingConnection)
 | |
|             {
 | |
|                 if (e.IsMouseDown(MouseButton.Left))
 | |
|                 {
 | |
|                     connectionEnd = mousePosition;
 | |
|                     NewUnitContextual();
 | |
|                     e.Use();
 | |
|                 }
 | |
|                 else if (e.IsFree(EventType.KeyDown) && e.keyCode == KeyCode.Escape)
 | |
|                 {
 | |
|                     CancelConnection();
 | |
|                     e.Use();
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             base.HandleHighPriorityInput();
 | |
|         }
 | |
| 
 | |
|         private void CompleteContextualConnection(IUnitPort source, IUnitPort destination)
 | |
|         {
 | |
|             source.ValidlyConnectTo(destination);
 | |
|             Cache();
 | |
|             var unitPosition = this.Widget<IUnitWidget>(destination.unit).position.position;
 | |
|             var portPosition = this.Widget<IUnitPortWidget>(destination).handlePosition.center.PixelPerfect();
 | |
|             var offset = portPosition - unitPosition;
 | |
|             destination.unit.position -= offset;
 | |
|             this.Widget(destination.unit).Reposition();
 | |
|             connectionSource = null;
 | |
|             GUI.changed = true;
 | |
|         }
 | |
| 
 | |
|         public void NewUnitContextual()
 | |
|         {
 | |
|             var filter = UnitOptionFilter.Any;
 | |
|             filter.GraphHashCode = graph.GetHashCode();
 | |
| 
 | |
|             if (connectionSource is ValueInput)
 | |
|             {
 | |
|                 var valueInput = (ValueInput)connectionSource;
 | |
|                 filter.CompatibleOutputType = valueInput.type;
 | |
|                 filter.Expose = false;
 | |
|                 filter.NoConnection = false;
 | |
|                 NewUnit(mousePosition, GetNewUnitOptions(filter), (unit) => CompleteContextualConnection(valueInput, unit.CompatibleValueOutput(valueInput.type)));
 | |
|             }
 | |
|             else if (connectionSource is ValueOutput)
 | |
|             {
 | |
|                 var valueOutput = (ValueOutput)connectionSource;
 | |
|                 filter.CompatibleInputType = valueOutput.type;
 | |
|                 filter.NoConnection = false;
 | |
|                 NewUnit(mousePosition, GetNewUnitOptions(filter), (unit) => CompleteContextualConnection(valueOutput, unit.CompatibleValueInput(valueOutput.type)));
 | |
|             }
 | |
|             else if (connectionSource is ControlInput)
 | |
|             {
 | |
|                 var controlInput = (ControlInput)connectionSource;
 | |
|                 filter.NoControlOutput = false;
 | |
|                 filter.NoConnection = false;
 | |
|                 NewUnit(mousePosition, GetNewUnitOptions(filter), (unit) => CompleteContextualConnection(controlInput, unit.controlOutputs.First()));
 | |
|             }
 | |
|             else if (connectionSource is ControlOutput)
 | |
|             {
 | |
|                 var controlOutput = (ControlOutput)connectionSource;
 | |
|                 filter.NoControlInput = false;
 | |
|                 filter.NoConnection = false;
 | |
|                 NewUnit(mousePosition, GetNewUnitOptions(filter), (unit) => CompleteContextualConnection(controlOutput, unit.controlInputs.First()));
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         #endregion
 | |
| 
 | |
| 
 | |
|         #region Context
 | |
| 
 | |
|         protected override void OnContext()
 | |
|         {
 | |
|             if (isCreatingConnection)
 | |
|             {
 | |
|                 CancelConnection();
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 // Checking for Alt seems to lose focus, for some reason maybe
 | |
|                 // unrelated to Bolt. Shift or other modifiers seem to work though.
 | |
|                 if (base.GetContextOptions().Any() && (!BoltFlow.Configuration.skipContextMenu || e.shift))
 | |
|                 {
 | |
|                     base.OnContext();
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     NewUnit(mousePosition);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         protected override IEnumerable<DropdownOption> GetContextOptions()
 | |
|         {
 | |
|             yield return new DropdownOption((Action<Vector2>)(NewUnit), "Add Node...");
 | |
|             yield return new DropdownOption((Action<Vector2>)(NewSticky), "Create Sticky Note");
 | |
|             foreach (var baseOption in base.GetContextOptions())
 | |
|             {
 | |
|                 yield return baseOption;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public void AddUnit(IUnit unit, Vector2 position)
 | |
|         {
 | |
|             UndoUtility.RecordEditedObject("Create Node");
 | |
|             unit.guid = Guid.NewGuid();
 | |
|             unit.position = position.PixelPerfect();
 | |
|             graph.units.Add(unit);
 | |
|             selection.Select(unit);
 | |
|             GUI.changed = true;
 | |
|         }
 | |
| 
 | |
|         private UnitOptionTree GetNewUnitOptions(UnitOptionFilter filter)
 | |
|         {
 | |
|             var options = new UnitOptionTree(new GUIContent("Node"));
 | |
| 
 | |
|             options.filter = filter;
 | |
|             options.reference = reference;
 | |
| 
 | |
|             if (filter.CompatibleOutputType == typeof(object))
 | |
|             {
 | |
|                 options.surfaceCommonTypeLiterals = true;
 | |
|             }
 | |
| 
 | |
|             return options;
 | |
|         }
 | |
| 
 | |
|         private void NewSticky(Vector2 position)
 | |
|         {
 | |
|             UndoUtility.RecordEditedObject("Create Sticky Note");
 | |
|             var stickyNote = new StickyNote() { position = new Rect(position, new Vector2(100, 100)) };
 | |
|             graph.elements.Add(stickyNote);
 | |
|             selection.Select(stickyNote);
 | |
|             GUI.changed = true;
 | |
|         }
 | |
| 
 | |
|         private void NewUnit(Vector2 position)
 | |
|         {
 | |
|             var filter = UnitOptionFilter.Any;
 | |
|             filter.GraphHashCode = graph.GetHashCode();
 | |
|             NewUnit(position, GetNewUnitOptions(filter));
 | |
|         }
 | |
| 
 | |
|         private void NewUnit(Vector2 unitPosition, UnitOptionTree options, Action<IUnit> then = null)
 | |
|         {
 | |
|             delayCall += () =>
 | |
|             {
 | |
|                 var activatorPosition = new Rect(e.mousePosition, new Vector2(200, 1));
 | |
| 
 | |
|                 var context = this.context;
 | |
| 
 | |
|                 LudiqGUI.FuzzyDropdown
 | |
|                     (
 | |
|                         activatorPosition,
 | |
|                         options,
 | |
|                         null,
 | |
|                         delegate (object _option)
 | |
|                         {
 | |
|                             context.BeginEdit();
 | |
|                             if (_option is IUnitOption)
 | |
|                             {
 | |
|                                 var option = (IUnitOption)_option;
 | |
|                                 var unit = option.InstantiateUnit();
 | |
|                                 AddUnit(unit, unitPosition);
 | |
|                                 option.PreconfigureUnit(unit);
 | |
|                                 then?.Invoke(unit);
 | |
|                                 GUI.changed = true;
 | |
|                             }
 | |
|                             else
 | |
|                             {
 | |
|                                 if ((Type)_option == typeof(StickyNote))
 | |
|                                 {
 | |
|                                     NewSticky(unitPosition);
 | |
|                                 }
 | |
|                             }
 | |
| 
 | |
|                             context.EndEdit();
 | |
|                         }
 | |
|                     );
 | |
|             };
 | |
|         }
 | |
| 
 | |
|         #endregion
 | |
| 
 | |
| 
 | |
|         #region Drag & Drop
 | |
| 
 | |
|         private bool CanDetermineDraggedInput(UnityObject uo)
 | |
|         {
 | |
|             if (uo.IsSceneBound())
 | |
|             {
 | |
|                 if (reference.self == uo.GameObject())
 | |
|                 {
 | |
|                     // Because we'll be able to assign it to Self
 | |
|                     return true;
 | |
|                 }
 | |
| 
 | |
|                 if (reference.serializedObject.IsSceneBound())
 | |
|                 {
 | |
|                     // Because we'll be able to use a direct scene reference
 | |
|                     return true;
 | |
|                 }
 | |
| 
 | |
|                 return false;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 return true;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public override bool AcceptsDragAndDrop()
 | |
|         {
 | |
|             if (DragAndDropUtility.Is<ScriptGraphAsset>())
 | |
|             {
 | |
|                 return FlowDragAndDropUtility.AcceptsScript(graph);
 | |
|             }
 | |
| 
 | |
|             return DragAndDropUtility.Is<UnityObject>() && !DragAndDropUtility.Is<IMacro>() && CanDetermineDraggedInput(DragAndDropUtility.Get<UnityObject>())
 | |
|                 || EditorVariablesUtility.isDraggingVariable;
 | |
|         }
 | |
| 
 | |
|         public override void PerformDragAndDrop()
 | |
|         {
 | |
|             if (DragAndDropUtility.Is<ScriptGraphAsset>())
 | |
|             {
 | |
|                 var flowMacro = DragAndDropUtility.Get<ScriptGraphAsset>();
 | |
|                 var superUnit = new SubgraphUnit(flowMacro);
 | |
|                 AddUnit(superUnit, DragAndDropUtility.position);
 | |
|             }
 | |
|             else if (DragAndDropUtility.Is<UnityObject>())
 | |
|             {
 | |
|                 var uo = DragAndDropUtility.Get<UnityObject>();
 | |
|                 var type = uo.GetType();
 | |
|                 var filter = UnitOptionFilter.Any;
 | |
|                 filter.Literals = false;
 | |
|                 filter.Expose = false;
 | |
|                 var options = GetNewUnitOptions(filter);
 | |
| 
 | |
|                 var root = new List<object>();
 | |
| 
 | |
|                 if (!uo.IsSceneBound() || reference.serializedObject.IsSceneBound())
 | |
|                 {
 | |
|                     if (uo == reference.self)
 | |
|                     {
 | |
|                         root.Add(new UnitOption<This>(new This()));
 | |
|                     }
 | |
| 
 | |
|                     root.Add(new LiteralOption(new Literal(type, uo)));
 | |
|                 }
 | |
| 
 | |
|                 if (uo is MonoScript script)
 | |
|                 {
 | |
|                     var scriptType = script.GetClass();
 | |
| 
 | |
|                     if (scriptType != null)
 | |
|                     {
 | |
|                         root.Add(scriptType);
 | |
|                     }
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     root.Add(type);
 | |
|                 }
 | |
| 
 | |
|                 if (uo is GameObject)
 | |
|                 {
 | |
|                     root.AddRange(uo.GetComponents<Component>().Select(c => c.GetType()));
 | |
|                 }
 | |
| 
 | |
|                 options.rootOverride = root.ToArray();
 | |
| 
 | |
|                 NewUnit(DragAndDropUtility.position, options, (unit) =>
 | |
|                 {
 | |
|                     // Try to assign a correct input
 | |
|                     var compatibleInput = unit.CompatibleValueInput(type);
 | |
| 
 | |
|                     if (compatibleInput == null)
 | |
|                     {
 | |
|                         return;
 | |
|                     }
 | |
| 
 | |
|                     if (uo.IsSceneBound())
 | |
|                     {
 | |
|                         if (reference.self == uo.GameObject())
 | |
|                         {
 | |
|                             // The component is owned by the same game object as the graph.
 | |
| 
 | |
|                             if (compatibleInput.nullMeansSelf)
 | |
|                             {
 | |
|                                 compatibleInput.SetDefaultValue(null);
 | |
|                             }
 | |
|                             else
 | |
|                             {
 | |
|                                 var self = new This();
 | |
|                                 self.position = unit.position + new Vector2(-150, 19);
 | |
|                                 graph.units.Add(self);
 | |
|                                 self.self.ConnectToValid(compatibleInput);
 | |
|                             }
 | |
|                         }
 | |
|                         else if (reference.serializedObject.IsSceneBound())
 | |
|                         {
 | |
|                             // The component is from another object from the same scene
 | |
|                             compatibleInput.SetDefaultValue(uo.ConvertTo(compatibleInput.type));
 | |
|                         }
 | |
|                         else
 | |
|                         {
 | |
|                             throw new NotSupportedException("Cannot determine compatible input from dragged Unity object.");
 | |
|                         }
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         compatibleInput.SetDefaultValue(uo.ConvertTo(compatibleInput.type));
 | |
|                     }
 | |
|                 });
 | |
|             }
 | |
|             else if (EditorVariablesUtility.isDraggingVariable)
 | |
|             {
 | |
|                 var kind = EditorVariablesUtility.kind;
 | |
|                 var declaration = EditorVariablesUtility.declaration;
 | |
| 
 | |
|                 UnifiedVariableUnit unit;
 | |
| 
 | |
|                 if (e.alt)
 | |
|                 {
 | |
|                     unit = new SetVariable();
 | |
|                 }
 | |
|                 else if (e.shift)
 | |
|                 {
 | |
|                     unit = new IsVariableDefined();
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     unit = new GetVariable();
 | |
|                 }
 | |
| 
 | |
|                 unit.kind = kind;
 | |
|                 AddUnit(unit, DragAndDropUtility.position);
 | |
|                 unit.name.SetDefaultValue(declaration.name);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public override void DrawDragAndDropPreview()
 | |
|         {
 | |
|             if (DragAndDropUtility.Is<ScriptGraphAsset>())
 | |
|             {
 | |
|                 GraphGUI.DrawDragAndDropPreviewLabel(DragAndDropUtility.offsetedPosition, DragAndDropUtility.Get<ScriptGraphAsset>().name, typeof(ScriptGraphAsset).Icon());
 | |
|             }
 | |
|             else if (DragAndDropUtility.Is<GameObject>())
 | |
|             {
 | |
|                 var gameObject = DragAndDropUtility.Get<GameObject>();
 | |
|                 GraphGUI.DrawDragAndDropPreviewLabel(DragAndDropUtility.offsetedPosition, gameObject.name + "...", gameObject.Icon());
 | |
|             }
 | |
|             else if (DragAndDropUtility.Is<UnityObject>())
 | |
|             {
 | |
|                 var obj = DragAndDropUtility.Get<UnityObject>();
 | |
|                 var type = obj.GetType();
 | |
|                 GraphGUI.DrawDragAndDropPreviewLabel(DragAndDropUtility.offsetedPosition, type.HumanName() + "...", type.Icon());
 | |
|             }
 | |
|             else if (EditorVariablesUtility.isDraggingVariable)
 | |
|             {
 | |
|                 var kind = EditorVariablesUtility.kind;
 | |
|                 var name = EditorVariablesUtility.declaration.name;
 | |
| 
 | |
|                 string label;
 | |
| 
 | |
|                 if (e.alt)
 | |
|                 {
 | |
|                     label = $"Set {name}";
 | |
|                 }
 | |
|                 else if (e.shift)
 | |
|                 {
 | |
|                     label = $"Check if {name} is defined";
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     label = $"Get {name}";
 | |
|                 }
 | |
| 
 | |
|                 GraphGUI.DrawDragAndDropPreviewLabel(DragAndDropUtility.offsetedPosition, label, BoltCore.Icons.VariableKind(kind));
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         #endregion
 | |
| 
 | |
| 
 | |
|         #region Drawing
 | |
| 
 | |
|         public bool showRelations { get; set; }
 | |
| 
 | |
|         #endregion
 | |
| 
 | |
| 
 | |
|         #region Connection Creation
 | |
| 
 | |
|         public IUnitPort connectionSource { get; set; }
 | |
| 
 | |
|         public Vector2 connectionEnd { get; set; }
 | |
| 
 | |
|         public bool isCreatingConnection => connectionSource != null &&
 | |
|         connectionSource.unit != null;                                     // Make sure the port didn't get destroyed: https://support.ludiq.io/communities/5/topics/4034-x
 | |
| 
 | |
|         public void CancelConnection()
 | |
|         {
 | |
|             connectionSource = null;
 | |
|         }
 | |
| 
 | |
|         #endregion
 | |
|     }
 | |
| }
 |