BaseNodeView.cs 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179
  1. using System.Collections.Generic;
  2. using UnityEngine;
  3. using UnityEditor.Experimental.GraphView;
  4. using UnityEngine.UIElements;
  5. using UnityEditor;
  6. using System.Reflection;
  7. using System;
  8. using System.Collections;
  9. using System.Linq;
  10. using UnityEditor.UIElements;
  11. using System.Text.RegularExpressions;
  12. using Status = UnityEngine.UIElements.DropdownMenuAction.Status;
  13. using NodeView = UnityEditor.Experimental.GraphView.Node;
  14. namespace GraphProcessor
  15. {
  16. [NodeCustomEditor(typeof(BaseNode))]
  17. public class BaseNodeView : NodeView
  18. {
  19. public BaseNode nodeTarget;
  20. public List< PortView > inputPortViews = new List< PortView >();
  21. public List< PortView > outputPortViews = new List< PortView >();
  22. public BaseGraphView owner { private set; get; }
  23. protected Dictionary< string, List< PortView > > portsPerFieldName = new Dictionary< string, List< PortView > >();
  24. public VisualElement controlsContainer;
  25. protected VisualElement debugContainer;
  26. protected VisualElement rightTitleContainer;
  27. protected VisualElement topPortContainer;
  28. protected VisualElement bottomPortContainer;
  29. private VisualElement inputContainerElement;
  30. VisualElement settings;
  31. NodeSettingsView settingsContainer;
  32. Button settingButton;
  33. TextField titleTextField;
  34. Label computeOrderLabel = new Label();
  35. public event Action< PortView > onPortConnected;
  36. public event Action< PortView > onPortDisconnected;
  37. protected virtual bool hasSettings { get; set; }
  38. public bool initializing = false; //Used for applying SetPosition on locked node at init.
  39. readonly string baseNodeStyle = "GraphProcessorStyles/BaseNodeView";
  40. bool settingsExpanded = false;
  41. [System.NonSerialized]
  42. List< IconBadge > badges = new List< IconBadge >();
  43. private List<Node> selectedNodes = new List<Node>();
  44. private float selectedNodesFarLeft;
  45. private float selectedNodesNearLeft;
  46. private float selectedNodesFarRight;
  47. private float selectedNodesNearRight;
  48. private float selectedNodesFarTop;
  49. private float selectedNodesNearTop;
  50. private float selectedNodesFarBottom;
  51. private float selectedNodesNearBottom;
  52. private float selectedNodesAvgHorizontal;
  53. private float selectedNodesAvgVertical;
  54. #region Initialization
  55. public void Initialize(BaseGraphView owner, BaseNode node)
  56. {
  57. nodeTarget = node;
  58. this.owner = owner;
  59. if (!node.deletable)
  60. capabilities &= ~Capabilities.Deletable;
  61. // Note that the Renamable capability is useless right now as it haven't been implemented in Graphview
  62. if (node.isRenamable)
  63. capabilities |= Capabilities.Renamable;
  64. owner.computeOrderUpdated += ComputeOrderUpdatedCallback;
  65. node.onMessageAdded += AddMessageView;
  66. node.onMessageRemoved += RemoveMessageView;
  67. node.onPortsUpdated += a => schedule.Execute(_ => UpdatePortsForField(a)).ExecuteLater(0);
  68. styleSheets.Add(Resources.Load<StyleSheet>(baseNodeStyle));
  69. if (!string.IsNullOrEmpty(node.layoutStyle))
  70. styleSheets.Add(Resources.Load<StyleSheet>(node.layoutStyle));
  71. InitializeView();
  72. InitializePorts();
  73. InitializeDebug();
  74. // If the standard Enable method is still overwritten, we call it
  75. if (GetType().GetMethod(nameof(Enable), new Type[]{}).DeclaringType != typeof(BaseNodeView))
  76. ExceptionToLog.Call(() => Enable());
  77. else
  78. ExceptionToLog.Call(() => Enable(false));
  79. InitializeSettings();
  80. RefreshExpandedState();
  81. this.RefreshPorts();
  82. RegisterCallback<GeometryChangedEvent>(OnGeometryChanged);
  83. RegisterCallback<DetachFromPanelEvent>(e => ExceptionToLog.Call(Disable));
  84. OnGeometryChanged(null);
  85. }
  86. void InitializePorts()
  87. {
  88. var listener = owner.connectorListener;
  89. foreach (var inputPort in nodeTarget.inputPorts)
  90. {
  91. AddPort(inputPort.fieldInfo, Direction.Input, listener, inputPort.portData);
  92. }
  93. foreach (var outputPort in nodeTarget.outputPorts)
  94. {
  95. AddPort(outputPort.fieldInfo, Direction.Output, listener, outputPort.portData);
  96. }
  97. }
  98. void InitializeView()
  99. {
  100. controlsContainer = new VisualElement{ name = "controls" };
  101. controlsContainer.AddToClassList("NodeControls");
  102. mainContainer.Add(controlsContainer);
  103. rightTitleContainer = new VisualElement{ name = "RightTitleContainer" };
  104. titleContainer.Add(rightTitleContainer);
  105. topPortContainer = new VisualElement { name = "TopPortContainer" };
  106. this.Insert(0, topPortContainer);
  107. bottomPortContainer = new VisualElement { name = "BottomPortContainer" };
  108. this.Add(bottomPortContainer);
  109. if (nodeTarget.showControlsOnHover)
  110. {
  111. bool mouseOverControls = false;
  112. controlsContainer.style.display = DisplayStyle.None;
  113. RegisterCallback<MouseOverEvent>(e => {
  114. controlsContainer.style.display = DisplayStyle.Flex;
  115. mouseOverControls = true;
  116. });
  117. RegisterCallback<MouseOutEvent>(e => {
  118. var rect = GetPosition();
  119. var graphMousePosition = owner.contentViewContainer.WorldToLocal(e.mousePosition);
  120. if (rect.Contains(graphMousePosition) || !nodeTarget.showControlsOnHover)
  121. return;
  122. mouseOverControls = false;
  123. schedule.Execute(_ => {
  124. if (!mouseOverControls)
  125. controlsContainer.style.display = DisplayStyle.None;
  126. }).ExecuteLater(500);
  127. });
  128. }
  129. Undo.undoRedoPerformed += UpdateFieldValues;
  130. debugContainer = new VisualElement{ name = "debug" };
  131. if (nodeTarget.debug)
  132. mainContainer.Add(debugContainer);
  133. initializing = true;
  134. UpdateTitle();
  135. SetPosition(nodeTarget.position);
  136. SetNodeColor(nodeTarget.color);
  137. AddInputContainer();
  138. // Add renaming capability
  139. if ((capabilities & Capabilities.Renamable) != 0)
  140. SetupRenamableTitle();
  141. }
  142. void SetupRenamableTitle()
  143. {
  144. var titleLabel = this.Q("title-label") as Label;
  145. titleTextField = new TextField{ isDelayed = true };
  146. titleTextField.style.display = DisplayStyle.None;
  147. titleLabel.parent.Insert(0, titleTextField);
  148. titleLabel.RegisterCallback<MouseDownEvent>(e => {
  149. if (e.clickCount == 2 && e.button == (int)MouseButton.LeftMouse)
  150. OpenTitleEditor();
  151. });
  152. titleTextField.RegisterValueChangedCallback(e => CloseAndSaveTitleEditor(e.newValue));
  153. titleTextField.RegisterCallback<MouseDownEvent>(e => {
  154. if (e.clickCount == 2 && e.button == (int)MouseButton.LeftMouse)
  155. CloseAndSaveTitleEditor(titleTextField.value);
  156. });
  157. titleTextField.RegisterCallback<FocusOutEvent>(e => CloseAndSaveTitleEditor(titleTextField.value));
  158. void OpenTitleEditor()
  159. {
  160. // show title textbox
  161. titleTextField.style.display = DisplayStyle.Flex;
  162. titleLabel.style.display = DisplayStyle.None;
  163. titleTextField.focusable = true;
  164. titleTextField.SetValueWithoutNotify(title);
  165. titleTextField.Focus();
  166. titleTextField.SelectAll();
  167. }
  168. void CloseAndSaveTitleEditor(string newTitle)
  169. {
  170. owner.RegisterCompleteObjectUndo("Renamed node " + newTitle);
  171. nodeTarget.SetCustomName(newTitle);
  172. // hide title TextBox
  173. titleTextField.style.display = DisplayStyle.None;
  174. titleLabel.style.display = DisplayStyle.Flex;
  175. titleTextField.focusable = false;
  176. UpdateTitle();
  177. }
  178. }
  179. void UpdateTitle()
  180. {
  181. title = (nodeTarget.GetCustomName() == null) ? nodeTarget.GetType().Name : nodeTarget.GetCustomName();
  182. }
  183. void InitializeSettings()
  184. {
  185. // Initialize settings button:
  186. if (hasSettings)
  187. {
  188. CreateSettingButton();
  189. settingsContainer = new NodeSettingsView();
  190. settingsContainer.visible = false;
  191. settings = new VisualElement();
  192. // Add Node type specific settings
  193. settings.Add(CreateSettingsView());
  194. settingsContainer.Add(settings);
  195. Add(settingsContainer);
  196. var fields = nodeTarget.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  197. foreach(var field in fields)
  198. if(field.GetCustomAttribute(typeof(SettingAttribute)) != null)
  199. AddSettingField(field);
  200. }
  201. }
  202. void OnGeometryChanged(GeometryChangedEvent evt)
  203. {
  204. if (settingButton != null)
  205. {
  206. var settingsButtonLayout = settingButton.ChangeCoordinatesTo(settingsContainer.parent, settingButton.layout);
  207. settingsContainer.style.top = settingsButtonLayout.yMax - 18f;
  208. settingsContainer.style.left = settingsButtonLayout.xMin - layout.width + 20f;
  209. }
  210. }
  211. // Workaround for bug in GraphView that makes the node selection border way too big
  212. VisualElement selectionBorder, nodeBorder;
  213. internal void EnableSyncSelectionBorderHeight()
  214. {
  215. if (selectionBorder == null || nodeBorder == null)
  216. {
  217. selectionBorder = this.Q("selection-border");
  218. nodeBorder = this.Q("node-border");
  219. schedule.Execute(() => {
  220. selectionBorder.style.height = nodeBorder.localBound.height;
  221. }).Every(17);
  222. }
  223. }
  224. void CreateSettingButton()
  225. {
  226. settingButton = new Button(ToggleSettings){name = "settings-button"};
  227. settingButton.Add(new Image { name = "icon", scaleMode = ScaleMode.ScaleToFit });
  228. titleContainer.Add(settingButton);
  229. }
  230. void ToggleSettings()
  231. {
  232. settingsExpanded = !settingsExpanded;
  233. if (settingsExpanded)
  234. OpenSettings();
  235. else
  236. CloseSettings();
  237. }
  238. public void OpenSettings()
  239. {
  240. if (settingsContainer != null)
  241. {
  242. owner.ClearSelection();
  243. owner.AddToSelection(this);
  244. settingButton.AddToClassList("clicked");
  245. settingsContainer.visible = true;
  246. settingsExpanded = true;
  247. }
  248. }
  249. public void CloseSettings()
  250. {
  251. if (settingsContainer != null)
  252. {
  253. settingButton.RemoveFromClassList("clicked");
  254. settingsContainer.visible = false;
  255. settingsExpanded = false;
  256. }
  257. }
  258. void InitializeDebug()
  259. {
  260. ComputeOrderUpdatedCallback();
  261. debugContainer.Add(computeOrderLabel);
  262. }
  263. #endregion
  264. #region API
  265. public List< PortView > GetPortViewsFromFieldName(string fieldName)
  266. {
  267. List< PortView > ret;
  268. portsPerFieldName.TryGetValue(fieldName, out ret);
  269. return ret;
  270. }
  271. public PortView GetFirstPortViewFromFieldName(string fieldName)
  272. {
  273. return GetPortViewsFromFieldName(fieldName)?.First();
  274. }
  275. public PortView GetPortViewFromFieldName(string fieldName, string identifier)
  276. {
  277. return GetPortViewsFromFieldName(fieldName)?.FirstOrDefault(pv => {
  278. return (pv.portData.identifier == identifier) || (String.IsNullOrEmpty(pv.portData.identifier) && String.IsNullOrEmpty(identifier));
  279. });
  280. }
  281. public PortView AddPort(FieldInfo fieldInfo, Direction direction, BaseEdgeConnectorListener listener, PortData portData)
  282. {
  283. PortView p = CreatePortView(direction, fieldInfo, portData, listener);
  284. if (p.direction == Direction.Input)
  285. {
  286. inputPortViews.Add(p);
  287. if (portData.vertical)
  288. topPortContainer.Add(p);
  289. else
  290. inputContainer.Add(p);
  291. }
  292. else
  293. {
  294. outputPortViews.Add(p);
  295. if (portData.vertical)
  296. bottomPortContainer.Add(p);
  297. else
  298. outputContainer.Add(p);
  299. }
  300. p.Initialize(this, portData?.displayName);
  301. List< PortView > ports;
  302. portsPerFieldName.TryGetValue(p.fieldName, out ports);
  303. if (ports == null)
  304. {
  305. ports = new List< PortView >();
  306. portsPerFieldName[p.fieldName] = ports;
  307. }
  308. ports.Add(p);
  309. return p;
  310. }
  311. protected virtual PortView CreatePortView(Direction direction, FieldInfo fieldInfo, PortData portData, BaseEdgeConnectorListener listener)
  312. => PortView.CreatePortView(direction, fieldInfo, portData, listener);
  313. public void InsertPort(PortView portView, int index)
  314. {
  315. if (portView.direction == Direction.Input)
  316. {
  317. if (portView.portData.vertical)
  318. topPortContainer.Insert(index, portView);
  319. else
  320. inputContainer.Insert(index, portView);
  321. }
  322. else
  323. {
  324. if (portView.portData.vertical)
  325. bottomPortContainer.Insert(index, portView);
  326. else
  327. outputContainer.Insert(index, portView);
  328. }
  329. }
  330. public void RemovePort(PortView p)
  331. {
  332. // Remove all connected edges:
  333. var edgesCopy = p.GetEdges().ToList();
  334. foreach (var e in edgesCopy)
  335. owner.Disconnect(e, refreshPorts: false);
  336. if (p.direction == Direction.Input)
  337. {
  338. if (inputPortViews.Remove(p))
  339. p.RemoveFromHierarchy();
  340. }
  341. else
  342. {
  343. if (outputPortViews.Remove(p))
  344. p.RemoveFromHierarchy();
  345. }
  346. List< PortView > ports;
  347. portsPerFieldName.TryGetValue(p.fieldName, out ports);
  348. ports.Remove(p);
  349. }
  350. private void SetValuesForSelectedNodes()
  351. {
  352. selectedNodes = new List<Node>();
  353. owner.nodes.ForEach(node =>
  354. {
  355. if(node.selected) selectedNodes.Add(node);
  356. });
  357. if(selectedNodes.Count < 2) return; // No need for any of the calculations below
  358. selectedNodesFarLeft = int.MinValue;
  359. selectedNodesFarRight = int.MinValue;
  360. selectedNodesFarTop = int.MinValue;
  361. selectedNodesFarBottom = int.MinValue;
  362. selectedNodesNearLeft = int.MaxValue;
  363. selectedNodesNearRight = int.MaxValue;
  364. selectedNodesNearTop = int.MaxValue;
  365. selectedNodesNearBottom = int.MaxValue;
  366. foreach(var selectedNode in selectedNodes)
  367. {
  368. var nodeStyle = selectedNode.style;
  369. var nodeWidth = selectedNode.localBound.size.x;
  370. var nodeHeight = selectedNode.localBound.size.y;
  371. if(nodeStyle.left.value.value > selectedNodesFarLeft) selectedNodesFarLeft = nodeStyle.left.value.value;
  372. if(nodeStyle.left.value.value + nodeWidth > selectedNodesFarRight) selectedNodesFarRight = nodeStyle.left.value.value + nodeWidth;
  373. if(nodeStyle.top.value.value > selectedNodesFarTop) selectedNodesFarTop = nodeStyle.top.value.value;
  374. if(nodeStyle.top.value.value + nodeHeight > selectedNodesFarBottom) selectedNodesFarBottom = nodeStyle.top.value.value + nodeHeight;
  375. if(nodeStyle.left.value.value < selectedNodesNearLeft) selectedNodesNearLeft = nodeStyle.left.value.value;
  376. if(nodeStyle.left.value.value + nodeWidth < selectedNodesNearRight) selectedNodesNearRight = nodeStyle.left.value.value + nodeWidth;
  377. if(nodeStyle.top.value.value < selectedNodesNearTop) selectedNodesNearTop = nodeStyle.top.value.value;
  378. if(nodeStyle.top.value.value + nodeHeight < selectedNodesNearBottom) selectedNodesNearBottom = nodeStyle.top.value.value + nodeHeight;
  379. }
  380. selectedNodesAvgHorizontal = (selectedNodesNearLeft + selectedNodesFarRight) / 2f;
  381. selectedNodesAvgVertical = (selectedNodesNearTop + selectedNodesFarBottom) / 2f;
  382. }
  383. public static Rect GetNodeRect(Node node, float left = int.MaxValue, float top = int.MaxValue)
  384. {
  385. return new Rect(
  386. new Vector2(left != int.MaxValue ? left : node.style.left.value.value, top != int.MaxValue ? top : node.style.top.value.value),
  387. new Vector2(node.style.width.value.value, node.style.height.value.value)
  388. );
  389. }
  390. public void AlignToLeft()
  391. {
  392. SetValuesForSelectedNodes();
  393. if(selectedNodes.Count < 2) return;
  394. foreach(var selectedNode in selectedNodes)
  395. {
  396. selectedNode.SetPosition(GetNodeRect(selectedNode, selectedNodesNearLeft));
  397. }
  398. }
  399. public void AlignToCenter()
  400. {
  401. SetValuesForSelectedNodes();
  402. if(selectedNodes.Count < 2) return;
  403. foreach(var selectedNode in selectedNodes)
  404. {
  405. selectedNode.SetPosition(GetNodeRect(selectedNode, selectedNodesAvgHorizontal - selectedNode.localBound.size.x / 2f));
  406. }
  407. }
  408. public void AlignToRight()
  409. {
  410. SetValuesForSelectedNodes();
  411. if(selectedNodes.Count < 2) return;
  412. foreach(var selectedNode in selectedNodes)
  413. {
  414. selectedNode.SetPosition(GetNodeRect(selectedNode, selectedNodesFarRight - selectedNode.localBound.size.x));
  415. }
  416. }
  417. public void AlignToTop()
  418. {
  419. SetValuesForSelectedNodes();
  420. if(selectedNodes.Count < 2) return;
  421. foreach(var selectedNode in selectedNodes)
  422. {
  423. selectedNode.SetPosition(GetNodeRect(selectedNode, top: selectedNodesNearTop));
  424. }
  425. }
  426. public void AlignToMiddle()
  427. {
  428. SetValuesForSelectedNodes();
  429. if(selectedNodes.Count < 2) return;
  430. foreach(var selectedNode in selectedNodes)
  431. {
  432. selectedNode.SetPosition(GetNodeRect(selectedNode, top: selectedNodesAvgVertical - selectedNode.localBound.size.y / 2f));
  433. }
  434. }
  435. public void AlignToBottom()
  436. {
  437. SetValuesForSelectedNodes();
  438. if(selectedNodes.Count < 2) return;
  439. foreach(var selectedNode in selectedNodes)
  440. {
  441. selectedNode.SetPosition(GetNodeRect(selectedNode, top: selectedNodesFarBottom - selectedNode.localBound.size.y));
  442. }
  443. }
  444. public void OpenNodeViewScript()
  445. {
  446. var script = NodeProvider.GetNodeViewScript(GetType());
  447. if (script != null)
  448. AssetDatabase.OpenAsset(script.GetInstanceID(), 0, 0);
  449. }
  450. public void OpenNodeScript()
  451. {
  452. var script = NodeProvider.GetNodeScript(nodeTarget.GetType());
  453. if (script != null)
  454. AssetDatabase.OpenAsset(script.GetInstanceID(), 0, 0);
  455. }
  456. public void ToggleDebug()
  457. {
  458. nodeTarget.debug = !nodeTarget.debug;
  459. UpdateDebugView();
  460. }
  461. public void UpdateDebugView()
  462. {
  463. if (nodeTarget.debug)
  464. mainContainer.Add(debugContainer);
  465. else
  466. mainContainer.Remove(debugContainer);
  467. }
  468. public void AddMessageView(string message, Texture icon, Color color)
  469. => AddBadge(new NodeBadgeView(message, icon, color));
  470. public void AddMessageView(string message, NodeMessageType messageType)
  471. {
  472. IconBadge badge = null;
  473. switch (messageType)
  474. {
  475. case NodeMessageType.Warning:
  476. badge = new NodeBadgeView(message, EditorGUIUtility.IconContent("Collab.Warning").image, Color.yellow);
  477. break ;
  478. case NodeMessageType.Error:
  479. badge = IconBadge.CreateError(message);
  480. break ;
  481. case NodeMessageType.Info:
  482. badge = IconBadge.CreateComment(message);
  483. break ;
  484. default:
  485. case NodeMessageType.None:
  486. badge = new NodeBadgeView(message, null, Color.grey);
  487. break ;
  488. }
  489. AddBadge(badge);
  490. }
  491. void AddBadge(IconBadge badge)
  492. {
  493. Add(badge);
  494. badges.Add(badge);
  495. badge.AttachTo(topContainer, SpriteAlignment.TopRight);
  496. }
  497. void RemoveBadge(Func<IconBadge, bool> callback)
  498. {
  499. badges.RemoveAll(b => {
  500. if (callback(b))
  501. {
  502. b.Detach();
  503. b.RemoveFromHierarchy();
  504. return true;
  505. }
  506. return false;
  507. });
  508. }
  509. public void RemoveMessageViewContains(string message) => RemoveBadge(b => b.badgeText.Contains(message));
  510. public void RemoveMessageView(string message) => RemoveBadge(b => b.badgeText == message);
  511. public void Highlight()
  512. {
  513. AddToClassList("Highlight");
  514. }
  515. public void UnHighlight()
  516. {
  517. RemoveFromClassList("Highlight");
  518. }
  519. #endregion
  520. #region Callbacks & Overrides
  521. void ComputeOrderUpdatedCallback()
  522. {
  523. //Update debug compute order
  524. computeOrderLabel.text = "Compute order: " + nodeTarget.computeOrder;
  525. }
  526. public virtual void Enable(bool fromInspector = false) => DrawDefaultInspector(fromInspector);
  527. public virtual void Enable() => DrawDefaultInspector(false);
  528. public virtual void Disable() {}
  529. Dictionary<string, List<(object value, VisualElement target)>> visibleConditions = new Dictionary<string, List<(object value, VisualElement target)>>();
  530. Dictionary<string, VisualElement> hideElementIfConnected = new Dictionary<string, VisualElement>();
  531. Dictionary<FieldInfo, List<VisualElement>> fieldControlsMap = new Dictionary<FieldInfo, List<VisualElement>>();
  532. protected void AddInputContainer()
  533. {
  534. inputContainerElement = new VisualElement {name = "input-container"};
  535. mainContainer.parent.Add(inputContainerElement);
  536. inputContainerElement.SendToBack();
  537. inputContainerElement.pickingMode = PickingMode.Ignore;
  538. }
  539. protected virtual void DrawDefaultInspector(bool fromInspector = false)
  540. {
  541. var fields = nodeTarget.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
  542. // Filter fields from the BaseNode type since we are only interested in user-defined fields
  543. // (better than BindingFlags.DeclaredOnly because we keep any inherited user-defined fields)
  544. .Where(f => f.DeclaringType != typeof(BaseNode));
  545. fields = nodeTarget.OverrideFieldOrder(fields).Reverse();
  546. foreach (var field in fields)
  547. {
  548. //skip if the field is a node setting
  549. if(field.GetCustomAttribute(typeof(SettingAttribute)) != null)
  550. {
  551. hasSettings = true;
  552. continue;
  553. }
  554. //skip if the field is not serializable
  555. bool serializeField = field.GetCustomAttribute(typeof(SerializeField)) != null;
  556. if((!field.IsPublic && !serializeField) || field.IsNotSerialized)
  557. {
  558. AddEmptyField(field, fromInspector);
  559. continue;
  560. }
  561. //skip if the field is an input/output and not marked as SerializedField
  562. bool hasInputAttribute = field.GetCustomAttribute(typeof(InputAttribute)) != null;
  563. bool hasInputOrOutputAttribute = hasInputAttribute || field.GetCustomAttribute(typeof(OutputAttribute)) != null;
  564. bool showAsDrawer = !fromInspector && field.GetCustomAttribute(typeof(ShowAsDrawer)) != null;
  565. if (!serializeField && hasInputOrOutputAttribute && !showAsDrawer)
  566. {
  567. AddEmptyField(field, fromInspector);
  568. continue;
  569. }
  570. //skip if marked with NonSerialized or HideInInspector
  571. if (field.GetCustomAttribute(typeof(System.NonSerializedAttribute)) != null || field.GetCustomAttribute(typeof(HideInInspector)) != null)
  572. {
  573. AddEmptyField(field, fromInspector);
  574. continue;
  575. }
  576. // Hide the field if we want to display in in the inspector
  577. var showInInspector = field.GetCustomAttribute<ShowInInspector>();
  578. if (!serializeField && showInInspector != null && !showInInspector.showInNode && !fromInspector)
  579. {
  580. AddEmptyField(field, fromInspector);
  581. continue;
  582. }
  583. var showInputDrawer = field.GetCustomAttribute(typeof(InputAttribute)) != null && field.GetCustomAttribute(typeof(SerializeField)) != null;
  584. showInputDrawer |= field.GetCustomAttribute(typeof(InputAttribute)) != null && field.GetCustomAttribute(typeof(ShowAsDrawer)) != null;
  585. showInputDrawer &= !fromInspector; // We can't show a drawer in the inspector
  586. showInputDrawer &= !typeof(IList).IsAssignableFrom(field.FieldType);
  587. string displayName = ObjectNames.NicifyVariableName(field.Name);
  588. var inspectorNameAttribute = field.GetCustomAttribute<InspectorNameAttribute>();
  589. if (inspectorNameAttribute != null)
  590. displayName = inspectorNameAttribute.displayName;
  591. var elem = AddControlField(field, displayName, showInputDrawer);
  592. if (hasInputAttribute)
  593. {
  594. hideElementIfConnected[field.Name] = elem;
  595. // Hide the field right away if there is already a connection:
  596. if (portsPerFieldName.TryGetValue(field.Name, out var pvs))
  597. if (pvs.Any(pv => pv.GetEdges().Count > 0))
  598. elem.style.display = DisplayStyle.None;
  599. }
  600. }
  601. }
  602. protected virtual void SetNodeColor(Color color)
  603. {
  604. titleContainer.style.borderBottomColor = new StyleColor(color);
  605. titleContainer.style.borderBottomWidth = new StyleFloat(color.a > 0 ? 5f : 0f);
  606. }
  607. private void AddEmptyField(FieldInfo field, bool fromInspector)
  608. {
  609. if (field.GetCustomAttribute(typeof(InputAttribute)) == null || fromInspector)
  610. return;
  611. if (field.GetCustomAttribute<VerticalAttribute>() != null)
  612. return;
  613. var box = new VisualElement {name = field.Name};
  614. box.AddToClassList("port-input-element");
  615. box.AddToClassList("empty");
  616. inputContainerElement.Add(box);
  617. }
  618. void UpdateFieldVisibility(string fieldName, object newValue)
  619. {
  620. if (newValue == null)
  621. return;
  622. if (visibleConditions.TryGetValue(fieldName, out var list))
  623. {
  624. foreach (var elem in list)
  625. {
  626. if (newValue.Equals(elem.value))
  627. elem.target.style.display = DisplayStyle.Flex;
  628. else
  629. elem.target.style.display = DisplayStyle.None;
  630. }
  631. }
  632. }
  633. void UpdateOtherFieldValueSpecific<T>(FieldInfo field, object newValue)
  634. {
  635. foreach (var inputField in fieldControlsMap[field])
  636. {
  637. var notify = inputField as INotifyValueChanged<T>;
  638. if (notify != null)
  639. notify.SetValueWithoutNotify((T)newValue);
  640. }
  641. }
  642. static MethodInfo specificUpdateOtherFieldValue = typeof(BaseNodeView).GetMethod(nameof(UpdateOtherFieldValueSpecific), BindingFlags.NonPublic | BindingFlags.Instance);
  643. void UpdateOtherFieldValue(FieldInfo info, object newValue)
  644. {
  645. // Warning: Keep in sync with FieldFactory CreateField
  646. var fieldType = info.FieldType.IsSubclassOf(typeof(UnityEngine.Object)) ? typeof(UnityEngine.Object) : info.FieldType;
  647. var genericUpdate = specificUpdateOtherFieldValue.MakeGenericMethod(fieldType);
  648. genericUpdate.Invoke(this, new object[]{info, newValue});
  649. }
  650. object GetInputFieldValueSpecific<T>(FieldInfo field)
  651. {
  652. if (fieldControlsMap.TryGetValue(field, out var list))
  653. {
  654. foreach (var inputField in list)
  655. {
  656. if (inputField is INotifyValueChanged<T> notify)
  657. return notify.value;
  658. }
  659. }
  660. return null;
  661. }
  662. static MethodInfo specificGetValue = typeof(BaseNodeView).GetMethod(nameof(GetInputFieldValueSpecific), BindingFlags.NonPublic | BindingFlags.Instance);
  663. object GetInputFieldValue(FieldInfo info)
  664. {
  665. // Warning: Keep in sync with FieldFactory CreateField
  666. var fieldType = info.FieldType.IsSubclassOf(typeof(UnityEngine.Object)) ? typeof(UnityEngine.Object) : info.FieldType;
  667. var genericUpdate = specificGetValue.MakeGenericMethod(fieldType);
  668. return genericUpdate.Invoke(this, new object[]{info});
  669. }
  670. protected VisualElement AddControlField(string fieldName, string label = null, bool showInputDrawer = false, Action valueChangedCallback = null)
  671. => AddControlField(nodeTarget.GetType().GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance), label, showInputDrawer, valueChangedCallback);
  672. Regex s_ReplaceNodeIndexPropertyPath = new Regex(@"(^nodes.Array.data\[)(\d+)(\])");
  673. internal void SyncSerializedPropertyPathes()
  674. {
  675. int nodeIndex = owner.graph.nodes.FindIndex(n => n == nodeTarget);
  676. // If the node is not found, then it means that it has been deleted from serialized data.
  677. if (nodeIndex == -1)
  678. return;
  679. var nodeIndexString = nodeIndex.ToString();
  680. foreach (var propertyField in this.Query<PropertyField>().ToList())
  681. {
  682. propertyField.Unbind();
  683. // The property path look like this: nodes.Array.data[x].fieldName
  684. // And we want to update the value of x with the new node index:
  685. propertyField.bindingPath = s_ReplaceNodeIndexPropertyPath.Replace(propertyField.bindingPath, m => m.Groups[1].Value + nodeIndexString + m.Groups[3].Value);
  686. propertyField.Bind(owner.serializedGraph);
  687. }
  688. }
  689. protected SerializedProperty FindSerializedProperty(string fieldName)
  690. {
  691. int i = owner.graph.nodes.FindIndex(n => n == nodeTarget);
  692. return owner.serializedGraph.FindProperty("nodes").GetArrayElementAtIndex(i).FindPropertyRelative(fieldName);
  693. }
  694. protected VisualElement AddControlField(FieldInfo field, string label = null, bool showInputDrawer = false, Action valueChangedCallback = null)
  695. {
  696. if (field == null)
  697. return null;
  698. var element = new PropertyField(FindSerializedProperty(field.Name), showInputDrawer ? "" : label);
  699. element.Bind(owner.serializedGraph);
  700. #if UNITY_2020_3 // In Unity 2020.3 the empty label on property field doesn't hide it, so we do it manually
  701. if ((showInputDrawer || String.IsNullOrEmpty(label)) && element != null)
  702. element.AddToClassList("DrawerField_2020_3");
  703. #endif
  704. if (typeof(IList).IsAssignableFrom(field.FieldType))
  705. EnableSyncSelectionBorderHeight();
  706. element.RegisterValueChangeCallback(e => {
  707. UpdateFieldVisibility(field.Name, field.GetValue(nodeTarget));
  708. valueChangedCallback?.Invoke();
  709. NotifyNodeChanged();
  710. });
  711. // Disallow picking scene objects when the graph is not linked to a scene
  712. if (element != null && !owner.graph.IsLinkedToScene())
  713. {
  714. var objectField = element.Q<ObjectField>();
  715. if (objectField != null)
  716. objectField.allowSceneObjects = false;
  717. }
  718. if (!fieldControlsMap.TryGetValue(field, out var inputFieldList))
  719. inputFieldList = fieldControlsMap[field] = new List<VisualElement>();
  720. inputFieldList.Add(element);
  721. if(element != null)
  722. {
  723. if (showInputDrawer)
  724. {
  725. var box = new VisualElement {name = field.Name};
  726. box.AddToClassList("port-input-element");
  727. box.Add(element);
  728. inputContainerElement.Add(box);
  729. }
  730. else
  731. {
  732. controlsContainer.Add(element);
  733. }
  734. element.name = field.Name;
  735. }
  736. else
  737. {
  738. // Make sure we create an empty placeholder if FieldFactory can not provide a drawer
  739. if (showInputDrawer) AddEmptyField(field, false);
  740. }
  741. var visibleCondition = field.GetCustomAttribute(typeof(VisibleIf)) as VisibleIf;
  742. if (visibleCondition != null)
  743. {
  744. // Check if target field exists:
  745. var conditionField = nodeTarget.GetType().GetField(visibleCondition.fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  746. if (conditionField == null)
  747. Debug.LogError($"[VisibleIf] Field {visibleCondition.fieldName} does not exists in node {nodeTarget.GetType()}");
  748. else
  749. {
  750. visibleConditions.TryGetValue(visibleCondition.fieldName, out var list);
  751. if (list == null)
  752. list = visibleConditions[visibleCondition.fieldName] = new List<(object value, VisualElement target)>();
  753. list.Add((visibleCondition.value, element));
  754. UpdateFieldVisibility(visibleCondition.fieldName, conditionField.GetValue(nodeTarget));
  755. }
  756. }
  757. return element;
  758. }
  759. void UpdateFieldValues()
  760. {
  761. foreach (var kp in fieldControlsMap)
  762. UpdateOtherFieldValue(kp.Key, kp.Key.GetValue(nodeTarget));
  763. }
  764. protected void AddSettingField(FieldInfo field)
  765. {
  766. if (field == null)
  767. return;
  768. var label = field.GetCustomAttribute<SettingAttribute>().name;
  769. var element = new PropertyField(FindSerializedProperty(field.Name));
  770. element.Bind(owner.serializedGraph);
  771. if (element != null)
  772. {
  773. settingsContainer.Add(element);
  774. element.name = field.Name;
  775. }
  776. }
  777. internal void OnPortConnected(PortView port)
  778. {
  779. if(port.direction == Direction.Input && inputContainerElement?.Q(port.fieldName) != null)
  780. inputContainerElement.Q(port.fieldName).AddToClassList("empty");
  781. if (hideElementIfConnected.TryGetValue(port.fieldName, out var elem))
  782. elem.style.display = DisplayStyle.None;
  783. onPortConnected?.Invoke(port);
  784. }
  785. internal void OnPortDisconnected(PortView port)
  786. {
  787. if (port.direction == Direction.Input && inputContainerElement?.Q(port.fieldName) != null)
  788. {
  789. inputContainerElement.Q(port.fieldName).RemoveFromClassList("empty");
  790. if (nodeTarget.nodeFields.TryGetValue(port.fieldName, out var fieldInfo))
  791. {
  792. var valueBeforeConnection = GetInputFieldValue(fieldInfo.info);
  793. if (valueBeforeConnection != null)
  794. {
  795. fieldInfo.info.SetValue(nodeTarget, valueBeforeConnection);
  796. }
  797. }
  798. }
  799. if (hideElementIfConnected.TryGetValue(port.fieldName, out var elem))
  800. elem.style.display = DisplayStyle.Flex;
  801. onPortDisconnected?.Invoke(port);
  802. }
  803. // TODO: a function to force to reload the custom behavior ports (if we want to do a button to add ports for example)
  804. public virtual void OnRemoved() {}
  805. public virtual void OnCreated() {}
  806. public override void SetPosition(Rect newPos)
  807. {
  808. if (initializing || !nodeTarget.isLocked)
  809. {
  810. base.SetPosition(newPos);
  811. if (!initializing)
  812. owner.RegisterCompleteObjectUndo("Moved graph node");
  813. nodeTarget.position = newPos;
  814. initializing = false;
  815. }
  816. }
  817. public override bool expanded
  818. {
  819. get { return base.expanded; }
  820. set
  821. {
  822. base.expanded = value;
  823. nodeTarget.expanded = value;
  824. }
  825. }
  826. public void ChangeLockStatus()
  827. {
  828. nodeTarget.nodeLock ^= true;
  829. }
  830. public override void BuildContextualMenu(ContextualMenuPopulateEvent evt)
  831. {
  832. BuildAlignMenu(evt);
  833. evt.menu.AppendAction("Open Node Script", (e) => OpenNodeScript(), OpenNodeScriptStatus);
  834. evt.menu.AppendAction("Open Node View Script", (e) => OpenNodeViewScript(), OpenNodeViewScriptStatus);
  835. evt.menu.AppendAction("Debug", (e) => ToggleDebug(), DebugStatus);
  836. if (nodeTarget.unlockable)
  837. evt.menu.AppendAction((nodeTarget.isLocked ? "Unlock" : "Lock"), (e) => ChangeLockStatus(), LockStatus);
  838. }
  839. protected void BuildAlignMenu(ContextualMenuPopulateEvent evt)
  840. {
  841. evt.menu.AppendAction("Align/To Left", (e) => AlignToLeft());
  842. evt.menu.AppendAction("Align/To Center", (e) => AlignToCenter());
  843. evt.menu.AppendAction("Align/To Right", (e) => AlignToRight());
  844. evt.menu.AppendSeparator("Align/");
  845. evt.menu.AppendAction("Align/To Top", (e) => AlignToTop());
  846. evt.menu.AppendAction("Align/To Middle", (e) => AlignToMiddle());
  847. evt.menu.AppendAction("Align/To Bottom", (e) => AlignToBottom());
  848. evt.menu.AppendSeparator();
  849. }
  850. Status LockStatus(DropdownMenuAction action)
  851. {
  852. return Status.Normal;
  853. }
  854. Status DebugStatus(DropdownMenuAction action)
  855. {
  856. if (nodeTarget.debug)
  857. return Status.Checked;
  858. return Status.Normal;
  859. }
  860. Status OpenNodeScriptStatus(DropdownMenuAction action)
  861. {
  862. if (NodeProvider.GetNodeScript(nodeTarget.GetType()) != null)
  863. return Status.Normal;
  864. return Status.Disabled;
  865. }
  866. Status OpenNodeViewScriptStatus(DropdownMenuAction action)
  867. {
  868. if (NodeProvider.GetNodeViewScript(GetType()) != null)
  869. return Status.Normal;
  870. return Status.Disabled;
  871. }
  872. IEnumerable< PortView > SyncPortCounts(IEnumerable< NodePort > ports, IEnumerable< PortView > portViews)
  873. {
  874. var listener = owner.connectorListener;
  875. var portViewList = portViews.ToList();
  876. // Maybe not good to remove ports as edges are still connected :/
  877. foreach (var pv in portViews.ToList())
  878. {
  879. // If the port have disappeared from the node data, we remove the view:
  880. // We can use the identifier here because this function will only be called when there is a custom port behavior
  881. if (!ports.Any(p => p.portData.identifier == pv.portData.identifier))
  882. {
  883. RemovePort(pv);
  884. portViewList.Remove(pv);
  885. }
  886. }
  887. foreach (var p in ports)
  888. {
  889. // Add missing port views
  890. if (!portViews.Any(pv => p.portData.identifier == pv.portData.identifier))
  891. {
  892. Direction portDirection = nodeTarget.IsFieldInput(p.fieldName) ? Direction.Input : Direction.Output;
  893. var pv = AddPort(p.fieldInfo, portDirection, listener, p.portData);
  894. portViewList.Add(pv);
  895. }
  896. }
  897. return portViewList;
  898. }
  899. void SyncPortOrder(IEnumerable< NodePort > ports, IEnumerable< PortView > portViews)
  900. {
  901. var portViewList = portViews.ToList();
  902. var portsList = ports.ToList();
  903. // Re-order the port views to match the ports order in case a custom behavior re-ordered the ports
  904. for (int i = 0; i < portsList.Count; i++)
  905. {
  906. var id = portsList[i].portData.identifier;
  907. var pv = portViewList.FirstOrDefault(p => p.portData.identifier == id);
  908. if (pv != null)
  909. InsertPort(pv, i);
  910. }
  911. }
  912. public virtual new bool RefreshPorts()
  913. {
  914. // If a port behavior was attached to one port, then
  915. // the port count might have been updated by the node
  916. // so we have to refresh the list of port views.
  917. UpdatePortViewWithPorts(nodeTarget.inputPorts, inputPortViews);
  918. UpdatePortViewWithPorts(nodeTarget.outputPorts, outputPortViews);
  919. void UpdatePortViewWithPorts(NodePortContainer ports, List< PortView > portViews)
  920. {
  921. if (ports.Count == 0 && portViews.Count == 0) // Nothing to update
  922. return;
  923. // When there is no current portviews, we can't zip the list so we just add all
  924. if (portViews.Count == 0)
  925. SyncPortCounts(ports, new PortView[]{});
  926. else if (ports.Count == 0) // Same when there is no ports
  927. SyncPortCounts(new NodePort[]{}, portViews);
  928. else if (portViews.Count != ports.Count)
  929. SyncPortCounts(ports, portViews);
  930. else
  931. {
  932. var p = ports.GroupBy(n => n.fieldName);
  933. var pv = portViews.GroupBy(v => v.fieldName);
  934. p.Zip(pv, (portPerFieldName, portViewPerFieldName) => {
  935. IEnumerable< PortView > portViewsList = portViewPerFieldName;
  936. if (portPerFieldName.Count() != portViewPerFieldName.Count())
  937. portViewsList = SyncPortCounts(portPerFieldName, portViewPerFieldName);
  938. SyncPortOrder(portPerFieldName, portViewsList);
  939. // We don't care about the result, we just iterate over port and portView
  940. return "";
  941. }).ToList();
  942. }
  943. // Here we're sure that we have the same amount of port and portView
  944. // so we can update the view with the new port data (if the name of a port have been changed for example)
  945. for (int i = 0; i < portViews.Count; i++)
  946. portViews[i].UpdatePortView(ports[i].portData);
  947. }
  948. return base.RefreshPorts();
  949. }
  950. public void ForceUpdatePorts()
  951. {
  952. nodeTarget.UpdateAllPorts();
  953. RefreshPorts();
  954. }
  955. void UpdatePortsForField(string fieldName)
  956. {
  957. // TODO: actual code
  958. RefreshPorts();
  959. }
  960. protected virtual VisualElement CreateSettingsView() => new Label("Settings") {name = "header"};
  961. /// <summary>
  962. /// Send an event to the graph telling that the content of this node have changed
  963. /// </summary>
  964. public void NotifyNodeChanged() => owner.graph.NotifyNodeChanged(nodeTarget);
  965. #endregion
  966. }
  967. }