| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179 | using System.Collections.Generic;using UnityEngine;using UnityEditor.Experimental.GraphView;using UnityEngine.UIElements;using UnityEditor;using System.Reflection;using System;using System.Collections;using System.Linq;using UnityEditor.UIElements;using System.Text.RegularExpressions;using Status = UnityEngine.UIElements.DropdownMenuAction.Status;using NodeView = UnityEditor.Experimental.GraphView.Node;namespace GraphProcessor{	[NodeCustomEditor(typeof(BaseNode))]	public class BaseNodeView : NodeView	{		public BaseNode							nodeTarget;		public List< PortView >					inputPortViews = new List< PortView >();		public List< PortView >					outputPortViews = new List< PortView >();		public BaseGraphView					owner { private set; get; }		protected Dictionary< string, List< PortView > > portsPerFieldName = new Dictionary< string, List< PortView > >();        public VisualElement 					controlsContainer;		protected VisualElement					debugContainer;		protected VisualElement					rightTitleContainer;		protected VisualElement					topPortContainer;		protected VisualElement					bottomPortContainer;		private VisualElement 					inputContainerElement;		VisualElement							settings;		NodeSettingsView						settingsContainer;		Button									settingButton;		TextField								titleTextField;		Label									computeOrderLabel = new Label();		public event Action< PortView >			onPortConnected;		public event Action< PortView >			onPortDisconnected;		protected virtual bool					hasSettings { get; set; }        public bool								initializing = false; //Used for applying SetPosition on locked node at init.        readonly string							baseNodeStyle = "GraphProcessorStyles/BaseNodeView";		bool									settingsExpanded = false;		[System.NonSerialized]		List< IconBadge >						badges = new List< IconBadge >();		private List<Node> selectedNodes = new List<Node>();		private float      selectedNodesFarLeft;		private float      selectedNodesNearLeft;		private float      selectedNodesFarRight;		private float      selectedNodesNearRight;		private float      selectedNodesFarTop;		private float      selectedNodesNearTop;		private float      selectedNodesFarBottom;		private float      selectedNodesNearBottom;		private float      selectedNodesAvgHorizontal;		private float      selectedNodesAvgVertical;				#region  Initialization				public void Initialize(BaseGraphView owner, BaseNode node)		{			nodeTarget = node;			this.owner = owner;			if (!node.deletable)				capabilities &= ~Capabilities.Deletable;			// Note that the Renamable capability is useless right now as it haven't been implemented in Graphview			if (node.isRenamable)				capabilities |= Capabilities.Renamable;			owner.computeOrderUpdated += ComputeOrderUpdatedCallback;			node.onMessageAdded += AddMessageView;			node.onMessageRemoved += RemoveMessageView;			node.onPortsUpdated += a => schedule.Execute(_ => UpdatePortsForField(a)).ExecuteLater(0);            styleSheets.Add(Resources.Load<StyleSheet>(baseNodeStyle));            if (!string.IsNullOrEmpty(node.layoutStyle))                styleSheets.Add(Resources.Load<StyleSheet>(node.layoutStyle));			InitializeView();			InitializePorts();			InitializeDebug();			// If the standard Enable method is still overwritten, we call it			if (GetType().GetMethod(nameof(Enable), new Type[]{}).DeclaringType != typeof(BaseNodeView))				ExceptionToLog.Call(() => Enable());			else				ExceptionToLog.Call(() => Enable(false));			InitializeSettings();			RefreshExpandedState();			this.RefreshPorts();			RegisterCallback<GeometryChangedEvent>(OnGeometryChanged);			RegisterCallback<DetachFromPanelEvent>(e => ExceptionToLog.Call(Disable));			OnGeometryChanged(null);		}		void InitializePorts()		{			var listener = owner.connectorListener;			foreach (var inputPort in nodeTarget.inputPorts)			{				AddPort(inputPort.fieldInfo, Direction.Input, listener, inputPort.portData);			}			foreach (var outputPort in nodeTarget.outputPorts)			{				AddPort(outputPort.fieldInfo, Direction.Output, listener, outputPort.portData);			}		}		void InitializeView()		{            controlsContainer = new VisualElement{ name = "controls" };			controlsContainer.AddToClassList("NodeControls");			mainContainer.Add(controlsContainer);			rightTitleContainer = new VisualElement{ name = "RightTitleContainer" };			titleContainer.Add(rightTitleContainer);			topPortContainer = new VisualElement { name = "TopPortContainer" };			this.Insert(0, topPortContainer);			bottomPortContainer = new VisualElement { name = "BottomPortContainer" };			this.Add(bottomPortContainer);			if (nodeTarget.showControlsOnHover)			{				bool mouseOverControls = false;				controlsContainer.style.display = DisplayStyle.None;				RegisterCallback<MouseOverEvent>(e => {					controlsContainer.style.display = DisplayStyle.Flex;					mouseOverControls = true;				});				RegisterCallback<MouseOutEvent>(e => {					var rect = GetPosition();					var graphMousePosition = owner.contentViewContainer.WorldToLocal(e.mousePosition);					if (rect.Contains(graphMousePosition) || !nodeTarget.showControlsOnHover)						return;					mouseOverControls = false;					schedule.Execute(_ => {						if (!mouseOverControls)							controlsContainer.style.display = DisplayStyle.None;					}).ExecuteLater(500);				});			}			Undo.undoRedoPerformed += UpdateFieldValues;			debugContainer = new VisualElement{ name = "debug" };			if (nodeTarget.debug)				mainContainer.Add(debugContainer);			initializing = true;			UpdateTitle();            SetPosition(nodeTarget.position);			SetNodeColor(nodeTarget.color);            			AddInputContainer();			// Add renaming capability			if ((capabilities & Capabilities.Renamable) != 0)				SetupRenamableTitle();		}		void SetupRenamableTitle()		{			var titleLabel = this.Q("title-label") as Label;			titleTextField = new TextField{ isDelayed = true };			titleTextField.style.display = DisplayStyle.None;			titleLabel.parent.Insert(0, titleTextField);			titleLabel.RegisterCallback<MouseDownEvent>(e => {				if (e.clickCount == 2 && e.button == (int)MouseButton.LeftMouse)					OpenTitleEditor();			});			titleTextField.RegisterValueChangedCallback(e => CloseAndSaveTitleEditor(e.newValue));			titleTextField.RegisterCallback<MouseDownEvent>(e => {				if (e.clickCount == 2 && e.button == (int)MouseButton.LeftMouse)					CloseAndSaveTitleEditor(titleTextField.value);			});			titleTextField.RegisterCallback<FocusOutEvent>(e => CloseAndSaveTitleEditor(titleTextField.value));			void OpenTitleEditor()			{				// show title textbox				titleTextField.style.display = DisplayStyle.Flex;				titleLabel.style.display = DisplayStyle.None;				titleTextField.focusable = true;				titleTextField.SetValueWithoutNotify(title);				titleTextField.Focus();				titleTextField.SelectAll();			}			void CloseAndSaveTitleEditor(string newTitle)			{				owner.RegisterCompleteObjectUndo("Renamed node " + newTitle);				nodeTarget.SetCustomName(newTitle);				// hide title TextBox				titleTextField.style.display = DisplayStyle.None;				titleLabel.style.display = DisplayStyle.Flex;				titleTextField.focusable = false;				UpdateTitle();			}		}		void UpdateTitle()		{			title = (nodeTarget.GetCustomName() == null) ? nodeTarget.GetType().Name : nodeTarget.GetCustomName();		}		void InitializeSettings()		{			// Initialize settings button:			if (hasSettings)			{				CreateSettingButton();				settingsContainer = new NodeSettingsView();				settingsContainer.visible = false;				settings = new VisualElement();				// Add Node type specific settings				settings.Add(CreateSettingsView());				settingsContainer.Add(settings);				Add(settingsContainer);								var fields = nodeTarget.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);				foreach(var field in fields)					if(field.GetCustomAttribute(typeof(SettingAttribute)) != null) 						AddSettingField(field);			}		}		void OnGeometryChanged(GeometryChangedEvent evt)		{			if (settingButton != null)			{				var settingsButtonLayout = settingButton.ChangeCoordinatesTo(settingsContainer.parent, settingButton.layout);				settingsContainer.style.top = settingsButtonLayout.yMax - 18f;				settingsContainer.style.left = settingsButtonLayout.xMin - layout.width + 20f;			}		}		// Workaround for bug in GraphView that makes the node selection border way too big		VisualElement selectionBorder, nodeBorder;		internal void EnableSyncSelectionBorderHeight()		{			if (selectionBorder == null || nodeBorder == null)			{				selectionBorder = this.Q("selection-border");				nodeBorder = this.Q("node-border");				schedule.Execute(() => {					selectionBorder.style.height = nodeBorder.localBound.height;				}).Every(17);			}		}				void CreateSettingButton()		{			settingButton = new Button(ToggleSettings){name = "settings-button"};			settingButton.Add(new Image { name = "icon", scaleMode = ScaleMode.ScaleToFit });			titleContainer.Add(settingButton);		}		void ToggleSettings()		{			settingsExpanded = !settingsExpanded;			if (settingsExpanded)				OpenSettings();			else				CloseSettings();		}		public void OpenSettings()		{			if (settingsContainer != null)			{				owner.ClearSelection();				owner.AddToSelection(this);				settingButton.AddToClassList("clicked");				settingsContainer.visible = true;				settingsExpanded = true;			}		}		public void CloseSettings()		{			if (settingsContainer != null)			{				settingButton.RemoveFromClassList("clicked");				settingsContainer.visible = false;				settingsExpanded = false;			}		}		void InitializeDebug()		{			ComputeOrderUpdatedCallback();			debugContainer.Add(computeOrderLabel);		}		#endregion		#region API		public List< PortView > GetPortViewsFromFieldName(string fieldName)		{			List< PortView >	ret;			portsPerFieldName.TryGetValue(fieldName, out ret);			return ret;		}		public PortView GetFirstPortViewFromFieldName(string fieldName)		{			return GetPortViewsFromFieldName(fieldName)?.First();		}		public PortView GetPortViewFromFieldName(string fieldName, string identifier)		{			return GetPortViewsFromFieldName(fieldName)?.FirstOrDefault(pv => {				return (pv.portData.identifier == identifier) || (String.IsNullOrEmpty(pv.portData.identifier) && String.IsNullOrEmpty(identifier));			});		}		public PortView AddPort(FieldInfo fieldInfo, Direction direction, BaseEdgeConnectorListener listener, PortData portData)		{			PortView p = CreatePortView(direction, fieldInfo, portData, listener);			if (p.direction == Direction.Input)			{				inputPortViews.Add(p);				if (portData.vertical)					topPortContainer.Add(p);				else					inputContainer.Add(p);			}			else			{				outputPortViews.Add(p);				if (portData.vertical)					bottomPortContainer.Add(p);				else					outputContainer.Add(p);			}			p.Initialize(this, portData?.displayName);			List< PortView > ports;			portsPerFieldName.TryGetValue(p.fieldName, out ports);			if (ports == null)			{				ports = new List< PortView >();				portsPerFieldName[p.fieldName] = ports;			}			ports.Add(p);			return p;		}        protected virtual PortView CreatePortView(Direction direction, FieldInfo fieldInfo, PortData portData, BaseEdgeConnectorListener listener)        	=> PortView.CreatePortView(direction, fieldInfo, portData, listener);        public void InsertPort(PortView portView, int index)		{			if (portView.direction == Direction.Input)			{				if (portView.portData.vertical)					topPortContainer.Insert(index, portView);				else					inputContainer.Insert(index, portView);			}			else			{				if (portView.portData.vertical)					bottomPortContainer.Insert(index, portView);				else					outputContainer.Insert(index, portView);			}		}		public void RemovePort(PortView p)		{			// Remove all connected edges:			var edgesCopy = p.GetEdges().ToList();			foreach (var e in edgesCopy)				owner.Disconnect(e, refreshPorts: false);			if (p.direction == Direction.Input)			{				if (inputPortViews.Remove(p))					p.RemoveFromHierarchy();			}			else			{				if (outputPortViews.Remove(p))					p.RemoveFromHierarchy();			}			List< PortView > ports;			portsPerFieldName.TryGetValue(p.fieldName, out ports);			ports.Remove(p);		}				private void SetValuesForSelectedNodes()		{			selectedNodes = new List<Node>();			owner.nodes.ForEach(node =>			{				if(node.selected) selectedNodes.Add(node);			});			if(selectedNodes.Count < 2) return; //	No need for any of the calculations below			selectedNodesFarLeft   = int.MinValue;			selectedNodesFarRight  = int.MinValue;			selectedNodesFarTop    = int.MinValue;			selectedNodesFarBottom = int.MinValue;			selectedNodesNearLeft   = int.MaxValue;			selectedNodesNearRight  = int.MaxValue;			selectedNodesNearTop    = int.MaxValue;			selectedNodesNearBottom = int.MaxValue;			foreach(var selectedNode in selectedNodes)			{				var nodeStyle  = selectedNode.style;				var nodeWidth  = selectedNode.localBound.size.x;				var nodeHeight = selectedNode.localBound.size.y;				if(nodeStyle.left.value.value > selectedNodesFarLeft) selectedNodesFarLeft                 = nodeStyle.left.value.value;				if(nodeStyle.left.value.value + nodeWidth > selectedNodesFarRight) selectedNodesFarRight   = nodeStyle.left.value.value + nodeWidth;				if(nodeStyle.top.value.value > selectedNodesFarTop) selectedNodesFarTop                    = nodeStyle.top.value.value;				if(nodeStyle.top.value.value + nodeHeight > selectedNodesFarBottom) selectedNodesFarBottom = nodeStyle.top.value.value + nodeHeight;				if(nodeStyle.left.value.value < selectedNodesNearLeft) selectedNodesNearLeft                 = nodeStyle.left.value.value;				if(nodeStyle.left.value.value + nodeWidth < selectedNodesNearRight) selectedNodesNearRight   = nodeStyle.left.value.value + nodeWidth;				if(nodeStyle.top.value.value < selectedNodesNearTop) selectedNodesNearTop                    = nodeStyle.top.value.value;				if(nodeStyle.top.value.value + nodeHeight < selectedNodesNearBottom) selectedNodesNearBottom = nodeStyle.top.value.value + nodeHeight;			}			selectedNodesAvgHorizontal = (selectedNodesNearLeft + selectedNodesFarRight) / 2f;			selectedNodesAvgVertical   = (selectedNodesNearTop + selectedNodesFarBottom) / 2f;		}		public static Rect GetNodeRect(Node node, float left = int.MaxValue, float top = int.MaxValue)		{			return new Rect(				new Vector2(left != int.MaxValue ? left : node.style.left.value.value, top != int.MaxValue ? top : node.style.top.value.value),				new Vector2(node.style.width.value.value, node.style.height.value.value)			);		}		public void AlignToLeft()		{			SetValuesForSelectedNodes();			if(selectedNodes.Count < 2) return;			foreach(var selectedNode in selectedNodes)			{				selectedNode.SetPosition(GetNodeRect(selectedNode, selectedNodesNearLeft));			}		}		public void AlignToCenter()		{			SetValuesForSelectedNodes();			if(selectedNodes.Count < 2) return;			foreach(var selectedNode in selectedNodes)			{				selectedNode.SetPosition(GetNodeRect(selectedNode, selectedNodesAvgHorizontal - selectedNode.localBound.size.x / 2f));			}		}		public void AlignToRight()		{			SetValuesForSelectedNodes();			if(selectedNodes.Count < 2) return;			foreach(var selectedNode in selectedNodes)			{				selectedNode.SetPosition(GetNodeRect(selectedNode, selectedNodesFarRight - selectedNode.localBound.size.x));			}		}		public void AlignToTop()		{			SetValuesForSelectedNodes();			if(selectedNodes.Count < 2) return;			foreach(var selectedNode in selectedNodes)			{				selectedNode.SetPosition(GetNodeRect(selectedNode, top: selectedNodesNearTop));			}		}		public void AlignToMiddle()		{			SetValuesForSelectedNodes();			if(selectedNodes.Count < 2) return;			foreach(var selectedNode in selectedNodes)			{				selectedNode.SetPosition(GetNodeRect(selectedNode, top: selectedNodesAvgVertical - selectedNode.localBound.size.y / 2f));			}		}		public void AlignToBottom()		{			SetValuesForSelectedNodes();			if(selectedNodes.Count < 2) return;			foreach(var selectedNode in selectedNodes)			{				selectedNode.SetPosition(GetNodeRect(selectedNode, top: selectedNodesFarBottom - selectedNode.localBound.size.y));			}		}		public void OpenNodeViewScript()		{			var script = NodeProvider.GetNodeViewScript(GetType());			if (script != null)				AssetDatabase.OpenAsset(script.GetInstanceID(), 0, 0);		}		public void OpenNodeScript()		{			var script = NodeProvider.GetNodeScript(nodeTarget.GetType());			if (script != null)				AssetDatabase.OpenAsset(script.GetInstanceID(), 0, 0);		}		public void ToggleDebug()		{			nodeTarget.debug = !nodeTarget.debug;			UpdateDebugView();		}		public void UpdateDebugView()		{			if (nodeTarget.debug)				mainContainer.Add(debugContainer);			else				mainContainer.Remove(debugContainer);		}		public void AddMessageView(string message, Texture icon, Color color)			=> AddBadge(new NodeBadgeView(message, icon, color));		public void AddMessageView(string message, NodeMessageType messageType)		{			IconBadge	badge = null;			switch (messageType)			{				case NodeMessageType.Warning:					badge = new NodeBadgeView(message, EditorGUIUtility.IconContent("Collab.Warning").image, Color.yellow);					break ;				case NodeMessageType.Error:						badge = IconBadge.CreateError(message);					break ;				case NodeMessageType.Info:					badge = IconBadge.CreateComment(message);					break ;				default:				case NodeMessageType.None:					badge = new NodeBadgeView(message, null, Color.grey);					break ;			}						AddBadge(badge);		}		void AddBadge(IconBadge badge)		{			Add(badge);			badges.Add(badge);			badge.AttachTo(topContainer, SpriteAlignment.TopRight);		}		void RemoveBadge(Func<IconBadge, bool> callback)		{			badges.RemoveAll(b => {				if (callback(b))				{					b.Detach();					b.RemoveFromHierarchy();					return true;				}				return false;			});		}		public void RemoveMessageViewContains(string message) => RemoveBadge(b => b.badgeText.Contains(message));				public void RemoveMessageView(string message) => RemoveBadge(b => b.badgeText == message);		public void Highlight()		{			AddToClassList("Highlight");		}		public void UnHighlight()		{			RemoveFromClassList("Highlight");		}		#endregion		#region Callbacks & Overrides		void ComputeOrderUpdatedCallback()		{			//Update debug compute order			computeOrderLabel.text = "Compute order: " + nodeTarget.computeOrder;		}		public virtual void Enable(bool fromInspector = false) => DrawDefaultInspector(fromInspector);		public virtual void Enable() => DrawDefaultInspector(false);		public virtual void Disable() {}		Dictionary<string, List<(object value, VisualElement target)>> visibleConditions = new Dictionary<string, List<(object value, VisualElement target)>>();		Dictionary<string, VisualElement>  hideElementIfConnected = new Dictionary<string, VisualElement>();		Dictionary<FieldInfo, List<VisualElement>> fieldControlsMap = new Dictionary<FieldInfo, List<VisualElement>>();		protected void AddInputContainer()		{			inputContainerElement = new VisualElement {name = "input-container"};			mainContainer.parent.Add(inputContainerElement);			inputContainerElement.SendToBack();			inputContainerElement.pickingMode = PickingMode.Ignore;		}		protected virtual void DrawDefaultInspector(bool fromInspector = false)		{			var fields = nodeTarget.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)				// Filter fields from the BaseNode type since we are only interested in user-defined fields				// (better than BindingFlags.DeclaredOnly because we keep any inherited user-defined fields) 				.Where(f => f.DeclaringType != typeof(BaseNode));			fields = nodeTarget.OverrideFieldOrder(fields).Reverse();			foreach (var field in fields)			{				//skip if the field is a node setting				if(field.GetCustomAttribute(typeof(SettingAttribute)) != null)				{					hasSettings = true;					continue;				}				//skip if the field is not serializable				bool serializeField = field.GetCustomAttribute(typeof(SerializeField)) != null;				if((!field.IsPublic && !serializeField) || field.IsNotSerialized)				{					AddEmptyField(field, fromInspector);					continue;				}				//skip if the field is an input/output and not marked as SerializedField				bool hasInputAttribute         = field.GetCustomAttribute(typeof(InputAttribute)) != null;				bool hasInputOrOutputAttribute = hasInputAttribute || field.GetCustomAttribute(typeof(OutputAttribute)) != null;				bool showAsDrawer			   = !fromInspector && field.GetCustomAttribute(typeof(ShowAsDrawer)) != null;				if (!serializeField && hasInputOrOutputAttribute && !showAsDrawer)				{					AddEmptyField(field, fromInspector);					continue;				}				//skip if marked with NonSerialized or HideInInspector				if (field.GetCustomAttribute(typeof(System.NonSerializedAttribute)) != null || field.GetCustomAttribute(typeof(HideInInspector)) != null)				{					AddEmptyField(field, fromInspector);					continue;				}				// Hide the field if we want to display in in the inspector				var showInInspector = field.GetCustomAttribute<ShowInInspector>();				if (!serializeField && showInInspector != null && !showInInspector.showInNode && !fromInspector)				{					AddEmptyField(field, fromInspector);					continue;				}				var showInputDrawer = field.GetCustomAttribute(typeof(InputAttribute)) != null && field.GetCustomAttribute(typeof(SerializeField)) != null;				showInputDrawer |= field.GetCustomAttribute(typeof(InputAttribute)) != null && field.GetCustomAttribute(typeof(ShowAsDrawer)) != null;				showInputDrawer &= !fromInspector; // We can't show a drawer in the inspector				showInputDrawer &= !typeof(IList).IsAssignableFrom(field.FieldType);				string displayName = ObjectNames.NicifyVariableName(field.Name);				var inspectorNameAttribute = field.GetCustomAttribute<InspectorNameAttribute>();				if (inspectorNameAttribute != null)					displayName = inspectorNameAttribute.displayName;				var elem = AddControlField(field, displayName, showInputDrawer);				if (hasInputAttribute)				{					hideElementIfConnected[field.Name] = elem;					// Hide the field right away if there is already a connection:					if (portsPerFieldName.TryGetValue(field.Name, out var pvs))						if (pvs.Any(pv => pv.GetEdges().Count > 0))							elem.style.display = DisplayStyle.None;				}			}		}		protected virtual void SetNodeColor(Color color)		{			titleContainer.style.borderBottomColor = new StyleColor(color);			titleContainer.style.borderBottomWidth = new StyleFloat(color.a > 0 ? 5f : 0f);		}				private void AddEmptyField(FieldInfo field, bool fromInspector)		{			if (field.GetCustomAttribute(typeof(InputAttribute)) == null || fromInspector)				return;			if (field.GetCustomAttribute<VerticalAttribute>() != null)				return;						var box = new VisualElement {name = field.Name};			box.AddToClassList("port-input-element");			box.AddToClassList("empty");			inputContainerElement.Add(box);		}		void UpdateFieldVisibility(string fieldName, object newValue)		{			if (newValue == null)				return;			if (visibleConditions.TryGetValue(fieldName, out var list))			{				foreach (var elem in list)				{					if (newValue.Equals(elem.value))						elem.target.style.display = DisplayStyle.Flex;					else						elem.target.style.display = DisplayStyle.None;				}			}		}		void UpdateOtherFieldValueSpecific<T>(FieldInfo field, object newValue)		{			foreach (var inputField in fieldControlsMap[field])			{				var notify = inputField as INotifyValueChanged<T>;				if (notify != null)					notify.SetValueWithoutNotify((T)newValue);			}		}		static MethodInfo specificUpdateOtherFieldValue = typeof(BaseNodeView).GetMethod(nameof(UpdateOtherFieldValueSpecific), BindingFlags.NonPublic | BindingFlags.Instance);		void UpdateOtherFieldValue(FieldInfo info, object newValue)		{			// Warning: Keep in sync with FieldFactory CreateField			var fieldType = info.FieldType.IsSubclassOf(typeof(UnityEngine.Object)) ? typeof(UnityEngine.Object) : info.FieldType;			var genericUpdate = specificUpdateOtherFieldValue.MakeGenericMethod(fieldType);			genericUpdate.Invoke(this, new object[]{info, newValue});		}		object GetInputFieldValueSpecific<T>(FieldInfo field)		{			if (fieldControlsMap.TryGetValue(field, out var list))			{				foreach (var inputField in list)				{					if (inputField is INotifyValueChanged<T> notify)						return notify.value;				}			}			return null;		}		static MethodInfo specificGetValue = typeof(BaseNodeView).GetMethod(nameof(GetInputFieldValueSpecific), BindingFlags.NonPublic | BindingFlags.Instance);		object GetInputFieldValue(FieldInfo info)		{			// Warning: Keep in sync with FieldFactory CreateField			var fieldType = info.FieldType.IsSubclassOf(typeof(UnityEngine.Object)) ? typeof(UnityEngine.Object) : info.FieldType;			var genericUpdate = specificGetValue.MakeGenericMethod(fieldType);			return genericUpdate.Invoke(this, new object[]{info});		}		protected VisualElement AddControlField(string fieldName, string label = null, bool showInputDrawer = false, Action valueChangedCallback = null)			=> AddControlField(nodeTarget.GetType().GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance), label, showInputDrawer, valueChangedCallback);		Regex s_ReplaceNodeIndexPropertyPath = new Regex(@"(^nodes.Array.data\[)(\d+)(\])");		internal void SyncSerializedPropertyPathes()		{			int nodeIndex = owner.graph.nodes.FindIndex(n => n == nodeTarget);			// If the node is not found, then it means that it has been deleted from serialized data.			if (nodeIndex == -1)				return;			var nodeIndexString = nodeIndex.ToString();			foreach (var propertyField in this.Query<PropertyField>().ToList())			{				propertyField.Unbind();				// The property path look like this: nodes.Array.data[x].fieldName				// And we want to update the value of x with the new node index:				propertyField.bindingPath = s_ReplaceNodeIndexPropertyPath.Replace(propertyField.bindingPath, m => m.Groups[1].Value + nodeIndexString + m.Groups[3].Value);				propertyField.Bind(owner.serializedGraph);			}		}		protected SerializedProperty FindSerializedProperty(string fieldName)		{			int i = owner.graph.nodes.FindIndex(n => n == nodeTarget);			return owner.serializedGraph.FindProperty("nodes").GetArrayElementAtIndex(i).FindPropertyRelative(fieldName);		}		protected VisualElement AddControlField(FieldInfo field, string label = null, bool showInputDrawer = false, Action valueChangedCallback = null)		{			if (field == null)				return null;			var element = new PropertyField(FindSerializedProperty(field.Name), showInputDrawer ? "" : label);			element.Bind(owner.serializedGraph);#if UNITY_2020_3 // In Unity 2020.3 the empty label on property field doesn't hide it, so we do it manually			if ((showInputDrawer || String.IsNullOrEmpty(label)) && element != null)				element.AddToClassList("DrawerField_2020_3");#endif			if (typeof(IList).IsAssignableFrom(field.FieldType))				EnableSyncSelectionBorderHeight();			element.RegisterValueChangeCallback(e => {				UpdateFieldVisibility(field.Name, field.GetValue(nodeTarget));				valueChangedCallback?.Invoke();				NotifyNodeChanged();			});			// Disallow picking scene objects when the graph is not linked to a scene			if (element != null && !owner.graph.IsLinkedToScene())			{				var objectField = element.Q<ObjectField>();				if (objectField != null)					objectField.allowSceneObjects = false;			}			if (!fieldControlsMap.TryGetValue(field, out var inputFieldList))				inputFieldList = fieldControlsMap[field] = new List<VisualElement>();			inputFieldList.Add(element);			if(element != null)			{				if (showInputDrawer)				{					var box = new VisualElement {name = field.Name};					box.AddToClassList("port-input-element");					box.Add(element);					inputContainerElement.Add(box);				}				else				{					controlsContainer.Add(element);				}				element.name = field.Name;			}			else			{				// Make sure we create an empty placeholder if FieldFactory can not provide a drawer				if (showInputDrawer) AddEmptyField(field, false);			}			var visibleCondition = field.GetCustomAttribute(typeof(VisibleIf)) as VisibleIf;			if (visibleCondition != null)			{				// Check if target field exists:				var conditionField = nodeTarget.GetType().GetField(visibleCondition.fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);				if (conditionField == null)					Debug.LogError($"[VisibleIf] Field {visibleCondition.fieldName} does not exists in node {nodeTarget.GetType()}");				else				{					visibleConditions.TryGetValue(visibleCondition.fieldName, out var list);					if (list == null)						list = visibleConditions[visibleCondition.fieldName] = new List<(object value, VisualElement target)>();					list.Add((visibleCondition.value, element));					UpdateFieldVisibility(visibleCondition.fieldName, conditionField.GetValue(nodeTarget));				}			}			return element;		}		void UpdateFieldValues()		{			foreach (var kp in fieldControlsMap)				UpdateOtherFieldValue(kp.Key, kp.Key.GetValue(nodeTarget));		}				protected void AddSettingField(FieldInfo field)		{			if (field == null)				return;			var label = field.GetCustomAttribute<SettingAttribute>().name;			var element = new PropertyField(FindSerializedProperty(field.Name));			element.Bind(owner.serializedGraph);			if (element != null)			{				settingsContainer.Add(element);				element.name = field.Name;			}		}		internal void OnPortConnected(PortView port)		{			if(port.direction == Direction.Input && inputContainerElement?.Q(port.fieldName) != null)				inputContainerElement.Q(port.fieldName).AddToClassList("empty");						if (hideElementIfConnected.TryGetValue(port.fieldName, out var elem))				elem.style.display = DisplayStyle.None;			onPortConnected?.Invoke(port);		}		internal void OnPortDisconnected(PortView port)		{			if (port.direction == Direction.Input && inputContainerElement?.Q(port.fieldName) != null)			{				inputContainerElement.Q(port.fieldName).RemoveFromClassList("empty");				if (nodeTarget.nodeFields.TryGetValue(port.fieldName, out var fieldInfo))				{					var valueBeforeConnection = GetInputFieldValue(fieldInfo.info);					if (valueBeforeConnection != null)					{						fieldInfo.info.SetValue(nodeTarget, valueBeforeConnection);					}				}			}						if (hideElementIfConnected.TryGetValue(port.fieldName, out var elem))				elem.style.display = DisplayStyle.Flex;			onPortDisconnected?.Invoke(port);		}		// TODO: a function to force to reload the custom behavior ports (if we want to do a button to add ports for example)		public virtual void OnRemoved() {}		public virtual void OnCreated() {}		public override void SetPosition(Rect newPos)		{            if (initializing || !nodeTarget.isLocked)            {                base.SetPosition(newPos);				if (!initializing)					owner.RegisterCompleteObjectUndo("Moved graph node");                nodeTarget.position = newPos;                initializing = false;            }		}		public override bool	expanded		{			get { return base.expanded; }			set			{				base.expanded = value;				nodeTarget.expanded = value;			}		}        public void ChangeLockStatus()        {            nodeTarget.nodeLock ^= true;        }        public override void BuildContextualMenu(ContextualMenuPopulateEvent evt)		{			BuildAlignMenu(evt);			evt.menu.AppendAction("Open Node Script", (e) => OpenNodeScript(), OpenNodeScriptStatus);			evt.menu.AppendAction("Open Node View Script", (e) => OpenNodeViewScript(), OpenNodeViewScriptStatus);			evt.menu.AppendAction("Debug", (e) => ToggleDebug(), DebugStatus);            if (nodeTarget.unlockable)                evt.menu.AppendAction((nodeTarget.isLocked ? "Unlock" : "Lock"), (e) => ChangeLockStatus(), LockStatus);        }		protected void BuildAlignMenu(ContextualMenuPopulateEvent evt)		{			evt.menu.AppendAction("Align/To Left", (e) => AlignToLeft());			evt.menu.AppendAction("Align/To Center", (e) => AlignToCenter());			evt.menu.AppendAction("Align/To Right", (e) => AlignToRight());			evt.menu.AppendSeparator("Align/");			evt.menu.AppendAction("Align/To Top", (e) => AlignToTop());			evt.menu.AppendAction("Align/To Middle", (e) => AlignToMiddle());			evt.menu.AppendAction("Align/To Bottom", (e) => AlignToBottom());			evt.menu.AppendSeparator();		}        Status LockStatus(DropdownMenuAction action)        {            return Status.Normal;        }        Status DebugStatus(DropdownMenuAction action)		{			if (nodeTarget.debug)				return Status.Checked;			return Status.Normal;		}		Status OpenNodeScriptStatus(DropdownMenuAction action)		{			if (NodeProvider.GetNodeScript(nodeTarget.GetType()) != null)				return Status.Normal;			return Status.Disabled;		}		Status OpenNodeViewScriptStatus(DropdownMenuAction action)		{			if (NodeProvider.GetNodeViewScript(GetType()) != null)				return Status.Normal;			return Status.Disabled;		}		IEnumerable< PortView > SyncPortCounts(IEnumerable< NodePort > ports, IEnumerable< PortView > portViews)		{			var listener = owner.connectorListener;			var portViewList = portViews.ToList();			// Maybe not good to remove ports as edges are still connected :/			foreach (var pv in portViews.ToList())			{				// If the port have disappeared from the node data, we remove the view:				// We can use the identifier here because this function will only be called when there is a custom port behavior				if (!ports.Any(p => p.portData.identifier == pv.portData.identifier))				{					RemovePort(pv);					portViewList.Remove(pv);				}			}			foreach (var p in ports)			{				// Add missing port views				if (!portViews.Any(pv => p.portData.identifier == pv.portData.identifier))				{					Direction portDirection = nodeTarget.IsFieldInput(p.fieldName) ? Direction.Input : Direction.Output;					var pv = AddPort(p.fieldInfo, portDirection, listener, p.portData);					portViewList.Add(pv);				}			}			return portViewList;		}		void SyncPortOrder(IEnumerable< NodePort > ports, IEnumerable< PortView > portViews)		{			var portViewList = portViews.ToList();			var portsList = ports.ToList();			// Re-order the port views to match the ports order in case a custom behavior re-ordered the ports			for (int i = 0; i < portsList.Count; i++)			{				var id = portsList[i].portData.identifier;				var pv = portViewList.FirstOrDefault(p => p.portData.identifier == id);				if (pv != null)					InsertPort(pv, i);			}		}		public virtual new bool RefreshPorts()		{			// If a port behavior was attached to one port, then			// the port count might have been updated by the node			// so we have to refresh the list of port views.			UpdatePortViewWithPorts(nodeTarget.inputPorts, inputPortViews);			UpdatePortViewWithPorts(nodeTarget.outputPorts, outputPortViews);			void UpdatePortViewWithPorts(NodePortContainer ports, List< PortView > portViews)			{				if (ports.Count == 0 && portViews.Count == 0) // Nothing to update					return;				// When there is no current portviews, we can't zip the list so we just add all				if (portViews.Count == 0)					SyncPortCounts(ports, new PortView[]{});				else if (ports.Count == 0) // Same when there is no ports					SyncPortCounts(new NodePort[]{}, portViews);				else if (portViews.Count != ports.Count)					SyncPortCounts(ports, portViews);				else				{					var p = ports.GroupBy(n => n.fieldName);					var pv = portViews.GroupBy(v => v.fieldName);					p.Zip(pv, (portPerFieldName, portViewPerFieldName) => {						IEnumerable< PortView > portViewsList = portViewPerFieldName;						if (portPerFieldName.Count() != portViewPerFieldName.Count())							portViewsList = SyncPortCounts(portPerFieldName, portViewPerFieldName);						SyncPortOrder(portPerFieldName, portViewsList);						// We don't care about the result, we just iterate over port and portView						return "";					}).ToList();				}				// Here we're sure that we have the same amount of port and portView				// so we can update the view with the new port data (if the name of a port have been changed for example)				for (int i = 0; i < portViews.Count; i++)					portViews[i].UpdatePortView(ports[i].portData);			}			return base.RefreshPorts();		}		public void ForceUpdatePorts()		{			nodeTarget.UpdateAllPorts();			RefreshPorts();		}		void UpdatePortsForField(string fieldName)		{			// TODO: actual code			RefreshPorts();		}		protected virtual VisualElement CreateSettingsView() => new Label("Settings") {name = "header"};		/// <summary>		/// Send an event to the graph telling that the content of this node have changed		/// </summary>		public void NotifyNodeChanged() => owner.graph.NotifyNodeChanged(nodeTarget);		#endregion    }}
 |