MaterialGraphView.cs 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using UnityEditor.Graphing.Util;
  5. using UnityEngine;
  6. using UnityEditor.Graphing;
  7. using Object = UnityEngine.Object;
  8. using UnityEditor.Graphs;
  9. using UnityEditor.Experimental.GraphView;
  10. using UnityEditor.ShaderGraph.Drawing.Colors;
  11. using UnityEditor.ShaderGraph.Internal;
  12. using UnityEngine.UIElements;
  13. using Edge = UnityEditor.Experimental.GraphView.Edge;
  14. using Node = UnityEditor.Experimental.GraphView.Node;
  15. namespace UnityEditor.ShaderGraph.Drawing
  16. {
  17. sealed class MaterialGraphView : GraphView
  18. {
  19. public MaterialGraphView()
  20. {
  21. styleSheets.Add(Resources.Load<StyleSheet>("Styles/MaterialGraphView"));
  22. serializeGraphElements = SerializeGraphElementsImplementation;
  23. canPasteSerializedData = CanPasteSerializedDataImplementation;
  24. unserializeAndPaste = UnserializeAndPasteImplementation;
  25. deleteSelection = DeleteSelectionImplementation;
  26. RegisterCallback<DragUpdatedEvent>(OnDragUpdatedEvent);
  27. RegisterCallback<DragPerformEvent>(OnDragPerformEvent);
  28. }
  29. protected override bool canCopySelection
  30. {
  31. get { return selection.OfType<IShaderNodeView>().Any(x => x.node.canCopyNode) || selection.OfType<Group>().Any() || selection.OfType<BlackboardField>().Any(); }
  32. }
  33. public MaterialGraphView(GraphData graph) : this()
  34. {
  35. this.graph = graph;
  36. }
  37. public GraphData graph { get; private set; }
  38. public Action onConvertToSubgraphClick { get; set; }
  39. public override List<Port> GetCompatiblePorts(Port startAnchor, NodeAdapter nodeAdapter)
  40. {
  41. var compatibleAnchors = new List<Port>();
  42. var startSlot = startAnchor.GetSlot();
  43. if (startSlot == null)
  44. return compatibleAnchors;
  45. var startStage = startSlot.stageCapability;
  46. if (startStage == ShaderStageCapability.All)
  47. startStage = NodeUtils.GetEffectiveShaderStageCapability(startSlot, true) & NodeUtils.GetEffectiveShaderStageCapability(startSlot, false);
  48. foreach (var candidateAnchor in ports.ToList())
  49. {
  50. var candidateSlot = candidateAnchor.GetSlot();
  51. if (!startSlot.IsCompatibleWith(candidateSlot))
  52. continue;
  53. if (startStage != ShaderStageCapability.All)
  54. {
  55. var candidateStage = candidateSlot.stageCapability;
  56. if (candidateStage == ShaderStageCapability.All)
  57. candidateStage = NodeUtils.GetEffectiveShaderStageCapability(candidateSlot, true)
  58. & NodeUtils.GetEffectiveShaderStageCapability(candidateSlot, false);
  59. if (candidateStage != ShaderStageCapability.All && candidateStage != startStage)
  60. continue;
  61. }
  62. compatibleAnchors.Add(candidateAnchor);
  63. }
  64. return compatibleAnchors;
  65. }
  66. public override void BuildContextualMenu(ContextualMenuPopulateEvent evt)
  67. {
  68. Vector2 mousePosition = evt.mousePosition;
  69. base.BuildContextualMenu(evt);
  70. if(evt.target is GraphView)
  71. {
  72. evt.menu.InsertAction(1, "Create Sticky Note", (e) => { AddStickyNote(mousePosition); });
  73. }
  74. if (evt.target is GraphView || evt.target is Node)
  75. {
  76. InitializeViewSubMenu(evt);
  77. evt.menu.AppendAction("Convert To Sub-graph", ConvertToSubgraph, ConvertToSubgraphStatus);
  78. evt.menu.AppendAction("Convert To Inline Node", ConvertToInlineNode, ConvertToInlineNodeStatus);
  79. evt.menu.AppendAction("Convert To Property", ConvertToProperty, ConvertToPropertyStatus);
  80. evt.menu.AppendAction("Group Selection", _ => GroupSelection(), (a) =>
  81. {
  82. List<ISelectable> filteredSelection = new List<ISelectable>();
  83. foreach (ISelectable selectedObject in selection)
  84. {
  85. if (selectedObject is Group)
  86. return DropdownMenuAction.Status.Disabled;
  87. VisualElement ve = selectedObject as VisualElement;
  88. if (ve.userData is AbstractMaterialNode)
  89. {
  90. var selectedNode = selectedObject as Node;
  91. if (selectedNode.GetContainingScope() is Group)
  92. return DropdownMenuAction.Status.Disabled;
  93. filteredSelection.Add(selectedObject);
  94. }
  95. }
  96. if (filteredSelection.Count > 0)
  97. return DropdownMenuAction.Status.Normal;
  98. else
  99. return DropdownMenuAction.Status.Disabled;
  100. });
  101. evt.menu.AppendAction("Ungroup Selection", RemoveFromGroupNode, (a) =>
  102. {
  103. List<ISelectable> filteredSelection = new List<ISelectable>();
  104. foreach (ISelectable selectedObject in selection)
  105. {
  106. if (selectedObject is Group)
  107. return DropdownMenuAction.Status.Disabled;
  108. VisualElement ve = selectedObject as VisualElement;
  109. if (ve.userData is AbstractMaterialNode)
  110. {
  111. var selectedNode = selectedObject as Node;
  112. if (selectedNode.GetContainingScope() is Group)
  113. filteredSelection.Add(selectedObject);
  114. }
  115. }
  116. if (filteredSelection.Count > 0)
  117. return DropdownMenuAction.Status.Normal;
  118. else
  119. return DropdownMenuAction.Status.Disabled;
  120. });
  121. var editorView = GetFirstAncestorOfType<GraphEditorView>();
  122. if (editorView.colorManager.activeSupportsCustom && selection.OfType<MaterialNodeView>().Any())
  123. {
  124. evt.menu.AppendSeparator();
  125. evt.menu.AppendAction("Color/Change...", ChangeCustomNodeColor,
  126. eventBase => DropdownMenuAction.Status.Normal);
  127. evt.menu.AppendAction("Color/Reset", menuAction =>
  128. {
  129. graph.owner.RegisterCompleteObjectUndo("Reset Node Color");
  130. foreach (var selectable in selection)
  131. {
  132. if (selectable is MaterialNodeView nodeView)
  133. {
  134. nodeView.node.ResetColor(editorView.colorManager.activeProviderName);
  135. editorView.colorManager.UpdateNodeView(nodeView);
  136. }
  137. }
  138. }, eventBase => DropdownMenuAction.Status.Normal);
  139. }
  140. if (selection.OfType<IShaderNodeView>().Count() == 1)
  141. {
  142. evt.menu.AppendSeparator();
  143. evt.menu.AppendAction("Open Documentation", SeeDocumentation, SeeDocumentationStatus);
  144. }
  145. if (selection.OfType<IShaderNodeView>().Count() == 1 && selection.OfType<IShaderNodeView>().First().node is SubGraphNode)
  146. {
  147. evt.menu.AppendSeparator();
  148. evt.menu.AppendAction("Open Sub Graph", OpenSubGraph, (a) => DropdownMenuAction.Status.Normal);
  149. }
  150. }
  151. if (evt.target is BlackboardField)
  152. {
  153. evt.menu.AppendAction("Delete", (e) => DeleteSelectionImplementation("Delete", AskUser.DontAskUser), (e) => canDeleteSelection ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Disabled);
  154. }
  155. if (evt.target is MaterialGraphView)
  156. {
  157. foreach (AbstractMaterialNode node in graph.GetNodes<AbstractMaterialNode>())
  158. {
  159. if (node.hasPreview && node.previewExpanded == true)
  160. evt.menu.AppendAction("Collapse All Previews", CollapsePreviews, (a) => DropdownMenuAction.Status.Normal);
  161. if (node.hasPreview && node.previewExpanded == false)
  162. evt.menu.AppendAction("Expand All Previews", ExpandPreviews, (a) => DropdownMenuAction.Status.Normal);
  163. }
  164. evt.menu.AppendSeparator();
  165. }
  166. }
  167. private void InitializeViewSubMenu(ContextualMenuPopulateEvent evt)
  168. {
  169. // Default the menu buttons to disabled
  170. DropdownMenuAction.Status expandPreviewAction = DropdownMenuAction.Status.Disabled;
  171. DropdownMenuAction.Status collapsePreviewAction = DropdownMenuAction.Status.Disabled;
  172. DropdownMenuAction.Status minimizeAction = DropdownMenuAction.Status.Disabled;
  173. DropdownMenuAction.Status maximizeAction = DropdownMenuAction.Status.Disabled;
  174. // Initialize strings
  175. string expandPreviewText = "View/Expand Previews";
  176. string collapsePreviewText = "View/Collapse Previews";
  177. string expandPortText = "View/Expand Ports";
  178. string collapsePortText = "View/Collapse Ports";
  179. if (selection.Count == 1)
  180. {
  181. collapsePreviewText = "View/Collapse Preview";
  182. expandPreviewText = "View/Expand Preview";
  183. }
  184. // Check if we can expand or collapse the ports/previews
  185. foreach (MaterialNodeView selectedNode in selection.Where(x => x is MaterialNodeView).Select(x => x as MaterialNodeView))
  186. {
  187. if (selectedNode.node.hasPreview)
  188. {
  189. if (selectedNode.node.previewExpanded)
  190. collapsePreviewAction = DropdownMenuAction.Status.Normal;
  191. else
  192. expandPreviewAction = DropdownMenuAction.Status.Normal;
  193. }
  194. if (selectedNode.CanToggleExpanded())
  195. {
  196. if (selectedNode.expanded)
  197. minimizeAction = DropdownMenuAction.Status.Normal;
  198. else
  199. maximizeAction = DropdownMenuAction.Status.Normal;
  200. }
  201. }
  202. // Create the menu options
  203. evt.menu.AppendAction(collapsePortText, _ => SetNodeExpandedOnSelection(false), (a) => minimizeAction);
  204. evt.menu.AppendAction(expandPortText, _ => SetNodeExpandedOnSelection(true), (a) => maximizeAction);
  205. evt.menu.AppendSeparator("View/");
  206. evt.menu.AppendAction(expandPreviewText, _ => SetPreviewExpandedOnSelection(true), (a) => expandPreviewAction);
  207. evt.menu.AppendAction(collapsePreviewText, _ => SetPreviewExpandedOnSelection(false), (a) => collapsePreviewAction);
  208. evt.menu.AppendSeparator();
  209. }
  210. void ChangeCustomNodeColor(DropdownMenuAction menuAction)
  211. {
  212. // Color Picker is internal :(
  213. var t = typeof(EditorWindow).Assembly.GetTypes().FirstOrDefault(ty => ty.Name == "ColorPicker");
  214. var m = t?.GetMethod("Show", new[] {typeof(Action<Color>), typeof(Color), typeof(bool), typeof(bool)});
  215. if (m == null)
  216. {
  217. Debug.LogWarning("Could not invoke Color Picker for ShaderGraph.");
  218. return;
  219. }
  220. var editorView = GetFirstAncestorOfType<GraphEditorView>();
  221. var defaultColor = Color.gray;
  222. if (selection.FirstOrDefault(sel => sel is MaterialNodeView) is MaterialNodeView selNode1)
  223. {
  224. defaultColor = selNode1.GetColor();
  225. defaultColor.a = 1.0f;
  226. }
  227. void ApplyColor(Color pickedColor)
  228. {
  229. foreach (var selectable in selection)
  230. {
  231. if(selectable is MaterialNodeView nodeView)
  232. {
  233. nodeView.node.SetColor(editorView.colorManager.activeProviderName, pickedColor);
  234. editorView.colorManager.UpdateNodeView(nodeView);
  235. }
  236. }
  237. }
  238. graph.owner.RegisterCompleteObjectUndo("Change Node Color");
  239. m.Invoke(null, new object[] {(Action<Color>) ApplyColor, defaultColor, true, false});
  240. }
  241. protected override bool canDeleteSelection
  242. {
  243. get
  244. {
  245. return selection.Any(x => !(x is IShaderNodeView nodeView) || nodeView.node.canDeleteNode);
  246. }
  247. }
  248. public void GroupSelection()
  249. {
  250. var title = "New Group";
  251. var groupData = new GroupData(title, new Vector2(10f,10f));
  252. graph.owner.RegisterCompleteObjectUndo("Create Group Node");
  253. graph.CreateGroup(groupData);
  254. foreach (var shaderNodeView in selection.OfType<IShaderNodeView>())
  255. {
  256. graph.SetGroup(shaderNodeView.node, groupData);
  257. }
  258. }
  259. public void AddStickyNote(Vector2 position)
  260. {
  261. position = contentViewContainer.WorldToLocal(position);
  262. string title = "New Note";
  263. string content = "Write something here";
  264. var stickyNoteData = new StickyNoteData(title, content, new Rect(position.x, position.y, 200, 160));
  265. graph.owner.RegisterCompleteObjectUndo("Create Sticky Note");
  266. graph.AddStickyNote(stickyNoteData);
  267. }
  268. void RemoveFromGroupNode(DropdownMenuAction action)
  269. {
  270. graph.owner.RegisterCompleteObjectUndo("Ungroup Node(s)");
  271. foreach (ISelectable selectable in selection)
  272. {
  273. var node = selectable as Node;
  274. if(node == null)
  275. continue;
  276. Group group = node.GetContainingScope() as Group;
  277. if (group != null)
  278. {
  279. group.RemoveElement(node);
  280. }
  281. }
  282. }
  283. public void SetNodeExpandedOnSelection(bool state)
  284. {
  285. graph.owner.RegisterCompleteObjectUndo("Toggle Expansion");
  286. foreach (MaterialNodeView selectedNode in selection.Where(x => x is MaterialNodeView).Select(x => x as MaterialNodeView))
  287. {
  288. if (selectedNode.CanToggleExpanded())
  289. {
  290. selectedNode.expanded = state;
  291. selectedNode.node.Dirty(ModificationScope.Topological);
  292. }
  293. }
  294. }
  295. public void SetPreviewExpandedOnSelection(bool state)
  296. {
  297. graph.owner.RegisterCompleteObjectUndo("Toggle Preview Visibility");
  298. foreach (MaterialNodeView selectedNode in selection.Where(x => x is MaterialNodeView).Select(x => x as MaterialNodeView))
  299. {
  300. selectedNode.node.previewExpanded = state;
  301. }
  302. }
  303. void CollapsePreviews(DropdownMenuAction action)
  304. {
  305. graph.owner.RegisterCompleteObjectUndo("Collapse Previews");
  306. foreach (AbstractMaterialNode node in graph.GetNodes<AbstractMaterialNode>())
  307. {
  308. node.previewExpanded = false;
  309. }
  310. }
  311. void ExpandPreviews(DropdownMenuAction action)
  312. {
  313. graph.owner.RegisterCompleteObjectUndo("Expand Previews");
  314. foreach (AbstractMaterialNode node in graph.GetNodes<AbstractMaterialNode>())
  315. {
  316. node.previewExpanded = true;
  317. }
  318. }
  319. void SeeDocumentation(DropdownMenuAction action)
  320. {
  321. var node = selection.OfType<IShaderNodeView>().First().node;
  322. if (node.documentationURL != null)
  323. System.Diagnostics.Process.Start(node.documentationURL);
  324. }
  325. void OpenSubGraph(DropdownMenuAction action)
  326. {
  327. SubGraphNode subgraphNode = selection.OfType<IShaderNodeView>().First().node as SubGraphNode;
  328. var path = AssetDatabase.GetAssetPath(subgraphNode.asset);
  329. ShaderGraphImporterEditor.ShowGraphEditWindow(path);
  330. }
  331. DropdownMenuAction.Status SeeDocumentationStatus(DropdownMenuAction action)
  332. {
  333. if (selection.OfType<IShaderNodeView>().First().node.documentationURL == null)
  334. return DropdownMenuAction.Status.Disabled;
  335. return DropdownMenuAction.Status.Normal;
  336. }
  337. DropdownMenuAction.Status ConvertToPropertyStatus(DropdownMenuAction action)
  338. {
  339. if (selection.OfType<IShaderNodeView>().Any(v => v.node != null))
  340. {
  341. if (selection.OfType<IShaderNodeView>().Any(v => v.node is IPropertyFromNode))
  342. return DropdownMenuAction.Status.Normal;
  343. return DropdownMenuAction.Status.Disabled;
  344. }
  345. return DropdownMenuAction.Status.Hidden;
  346. }
  347. void ConvertToProperty(DropdownMenuAction action)
  348. {
  349. graph.owner.RegisterCompleteObjectUndo("Convert to Property");
  350. var selectedNodeViews = selection.OfType<IShaderNodeView>().Select(x => x.node).ToList();
  351. foreach (var node in selectedNodeViews)
  352. {
  353. if (!(node is IPropertyFromNode))
  354. continue;
  355. var converter = node as IPropertyFromNode;
  356. var prop = converter.AsShaderProperty();
  357. graph.SanitizeGraphInputName(prop);
  358. graph.AddGraphInput(prop);
  359. var propNode = new PropertyNode();
  360. propNode.drawState = node.drawState;
  361. propNode.groupGuid = node.groupGuid;
  362. graph.AddNode(propNode);
  363. propNode.propertyGuid = prop.guid;
  364. var oldSlot = node.FindSlot<MaterialSlot>(converter.outputSlotId);
  365. var newSlot = propNode.FindSlot<MaterialSlot>(PropertyNode.OutputSlotId);
  366. foreach (var edge in graph.GetEdges(oldSlot.slotReference))
  367. graph.Connect(newSlot.slotReference, edge.inputSlot);
  368. graph.RemoveNode(node);
  369. }
  370. }
  371. DropdownMenuAction.Status ConvertToInlineNodeStatus(DropdownMenuAction action)
  372. {
  373. if (selection.OfType<IShaderNodeView>().Any(v => v.node != null))
  374. {
  375. if (selection.OfType<IShaderNodeView>().Any(v => v.node is PropertyNode))
  376. return DropdownMenuAction.Status.Normal;
  377. return DropdownMenuAction.Status.Disabled;
  378. }
  379. return DropdownMenuAction.Status.Hidden;
  380. }
  381. void ConvertToInlineNode(DropdownMenuAction action)
  382. {
  383. graph.owner.RegisterCompleteObjectUndo("Convert to Inline Node");
  384. var selectedNodeViews = selection.OfType<IShaderNodeView>()
  385. .Select(x => x.node)
  386. .OfType<PropertyNode>();
  387. foreach (var propNode in selectedNodeViews)
  388. ((GraphData)propNode.owner).ReplacePropertyNodeWithConcreteNode(propNode);
  389. }
  390. DropdownMenuAction.Status ConvertToSubgraphStatus(DropdownMenuAction action)
  391. {
  392. if (onConvertToSubgraphClick == null) return DropdownMenuAction.Status.Hidden;
  393. return selection.OfType<IShaderNodeView>().Any(v => v.node != null && v.node.allowedInSubGraph && !(v.node is SubGraphOutputNode) ) ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Hidden;
  394. }
  395. void ConvertToSubgraph(DropdownMenuAction action)
  396. {
  397. onConvertToSubgraphClick();
  398. }
  399. string SerializeGraphElementsImplementation(IEnumerable<GraphElement> elements)
  400. {
  401. var groups = elements.OfType<ShaderGroup>().Select(x => x.userData);
  402. var nodes = elements.OfType<IShaderNodeView>().Select(x => x.node).Where(x => x.canCopyNode);
  403. var edges = elements.OfType<Edge>().Select(x => x.userData).OfType<IEdge>();
  404. var inputs = selection.OfType<BlackboardField>().Select(x => x.userData as ShaderInput);
  405. var notes = elements.OfType<StickyNote>().Select(x => x.userData);
  406. // Collect the property nodes and get the corresponding properties
  407. var propertyNodeGuids = nodes.OfType<PropertyNode>().Select(x => x.propertyGuid);
  408. var metaProperties = this.graph.properties.Where(x => propertyNodeGuids.Contains(x.guid));
  409. // Collect the keyword nodes and get the corresponding keywords
  410. var keywordNodeGuids = nodes.OfType<KeywordNode>().Select(x => x.keywordGuid);
  411. var metaKeywords = this.graph.keywords.Where(x => keywordNodeGuids.Contains(x.guid));
  412. var graph = new CopyPasteGraph(this.graph.assetGuid, groups, nodes, edges, inputs, metaProperties, metaKeywords, notes);
  413. return JsonUtility.ToJson(graph, true);
  414. }
  415. bool CanPasteSerializedDataImplementation(string serializedData)
  416. {
  417. return CopyPasteGraph.FromJson(serializedData) != null;
  418. }
  419. void UnserializeAndPasteImplementation(string operationName, string serializedData)
  420. {
  421. graph.owner.RegisterCompleteObjectUndo(operationName);
  422. var pastedGraph = CopyPasteGraph.FromJson(serializedData);
  423. this.InsertCopyPasteGraph(pastedGraph);
  424. }
  425. void DeleteSelectionImplementation(string operationName, GraphView.AskUser askUser)
  426. {
  427. bool containsProperty = false;
  428. // Keywords need to be tested against variant limit based on multiple factors
  429. bool keywordsDirty = false;
  430. // Track dependent keyword nodes to remove them
  431. List<KeywordNode> keywordNodes = new List<KeywordNode>();
  432. foreach (var selectable in selection)
  433. {
  434. var field = selectable as BlackboardField;
  435. if (field != null && field.userData != null)
  436. {
  437. switch(field.userData)
  438. {
  439. case AbstractShaderProperty property:
  440. containsProperty = true;
  441. break;
  442. case ShaderKeyword keyword:
  443. keywordNodes.AddRange(graph.GetNodes<KeywordNode>().Where(x => x.keywordGuid == keyword.guid));
  444. break;
  445. default:
  446. throw new ArgumentOutOfRangeException();
  447. }
  448. }
  449. }
  450. if(containsProperty)
  451. {
  452. if (graph.isSubGraph)
  453. {
  454. if (!EditorUtility.DisplayDialog("Sub Graph Will Change", "If you remove a property and save the sub graph, you might change other graphs that are using this sub graph.\n\nDo you want to continue?", "Yes", "No"))
  455. return;
  456. }
  457. }
  458. // Filter nodes that cannot be deleted
  459. var nodesToDelete = selection.OfType<IShaderNodeView>().Where(v => !(v.node is SubGraphOutputNode) && v.node.canDeleteNode).Select(x => x.node);
  460. // Add keyword nodes dependent on deleted keywords
  461. nodesToDelete = nodesToDelete.Union(keywordNodes);
  462. // If deleting a Sub Graph node whose asset contains Keywords test variant limit
  463. foreach(SubGraphNode subGraphNode in nodesToDelete.OfType<SubGraphNode>())
  464. {
  465. if (subGraphNode.asset == null)
  466. {
  467. continue;
  468. }
  469. if(subGraphNode.asset.keywords.Count > 0)
  470. {
  471. keywordsDirty = true;
  472. }
  473. }
  474. graph.owner.RegisterCompleteObjectUndo(operationName);
  475. graph.RemoveElements(nodesToDelete.ToArray(),
  476. selection.OfType<Edge>().Select(x => x.userData).OfType<IEdge>().ToArray(),
  477. selection.OfType<ShaderGroup>().Select(x => x.userData).ToArray(),
  478. selection.OfType<StickyNote>().Select(x => x.userData).ToArray());
  479. foreach (var selectable in selection)
  480. {
  481. var field = selectable as BlackboardField;
  482. if (field != null && field.userData != null)
  483. {
  484. var input = (ShaderInput)field.userData;
  485. graph.RemoveGraphInput(input);
  486. // If deleting a Keyword test variant limit
  487. if(input is ShaderKeyword keyword)
  488. {
  489. keywordsDirty = true;
  490. }
  491. }
  492. }
  493. // Test Keywords against variant limit
  494. if(keywordsDirty)
  495. {
  496. graph.OnKeywordChangedNoValidate();
  497. }
  498. selection.Clear();
  499. }
  500. #region Drag and drop
  501. bool ValidateObjectForDrop(Object obj)
  502. {
  503. return EditorUtility.IsPersistent(obj) && (
  504. obj is Texture2D ||
  505. obj is Cubemap ||
  506. obj is SubGraphAsset asset && !asset.descendents.Contains(graph.assetGuid) && asset.assetGuid != graph.assetGuid ||
  507. obj is Texture2DArray ||
  508. obj is Texture3D);
  509. }
  510. void OnDragUpdatedEvent(DragUpdatedEvent e)
  511. {
  512. var selection = DragAndDrop.GetGenericData("DragSelection") as List<ISelectable>;
  513. bool dragging = false;
  514. if (selection != null)
  515. {
  516. // Blackboard
  517. if (selection.OfType<BlackboardField>().Any())
  518. dragging = true;
  519. }
  520. else
  521. {
  522. // Handle unity objects
  523. var objects = DragAndDrop.objectReferences;
  524. foreach (Object obj in objects)
  525. {
  526. if (ValidateObjectForDrop(obj))
  527. {
  528. dragging = true;
  529. break;
  530. }
  531. }
  532. }
  533. if (dragging)
  534. {
  535. DragAndDrop.visualMode = DragAndDropVisualMode.Generic;
  536. }
  537. }
  538. void OnDragPerformEvent(DragPerformEvent e)
  539. {
  540. Vector2 localPos = (e.currentTarget as VisualElement).ChangeCoordinatesTo(contentViewContainer, e.localMousePosition);
  541. var selection = DragAndDrop.GetGenericData("DragSelection") as List<ISelectable>;
  542. if (selection != null)
  543. {
  544. // Blackboard
  545. if (selection.OfType<BlackboardField>().Any())
  546. {
  547. IEnumerable<BlackboardField> fields = selection.OfType<BlackboardField>();
  548. foreach (BlackboardField field in fields)
  549. {
  550. CreateNode(field, localPos);
  551. }
  552. }
  553. }
  554. else
  555. {
  556. // Handle unity objects
  557. var objects = DragAndDrop.objectReferences;
  558. foreach (Object obj in objects)
  559. {
  560. if (ValidateObjectForDrop(obj))
  561. {
  562. CreateNode(obj, localPos);
  563. }
  564. }
  565. }
  566. }
  567. void CreateNode(object obj, Vector2 nodePosition)
  568. {
  569. var texture2D = obj as Texture2D;
  570. if (texture2D != null)
  571. {
  572. graph.owner.RegisterCompleteObjectUndo("Drag Texture");
  573. bool isNormalMap = false;
  574. if (EditorUtility.IsPersistent(texture2D) && !string.IsNullOrEmpty(AssetDatabase.GetAssetPath(texture2D)))
  575. {
  576. var importer = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(texture2D)) as TextureImporter;
  577. if (importer != null)
  578. isNormalMap = importer.textureType == TextureImporterType.NormalMap;
  579. }
  580. var node = new SampleTexture2DNode();
  581. var drawState = node.drawState;
  582. drawState.position = new Rect(nodePosition, drawState.position.size);
  583. node.drawState = drawState;
  584. graph.AddNode(node);
  585. if (isNormalMap)
  586. node.textureType = TextureType.Normal;
  587. var inputslot = node.FindInputSlot<Texture2DInputMaterialSlot>(SampleTexture2DNode.TextureInputId);
  588. if (inputslot != null)
  589. inputslot.texture = texture2D;
  590. }
  591. var textureArray = obj as Texture2DArray;
  592. if (textureArray != null)
  593. {
  594. graph.owner.RegisterCompleteObjectUndo("Drag Texture Array");
  595. var node = new SampleTexture2DArrayNode();
  596. var drawState = node.drawState;
  597. drawState.position = new Rect(nodePosition, drawState.position.size);
  598. node.drawState = drawState;
  599. graph.AddNode(node);
  600. var inputslot = node.FindSlot<Texture2DArrayInputMaterialSlot>(SampleTexture2DArrayNode.TextureInputId);
  601. if (inputslot != null)
  602. inputslot.textureArray = textureArray;
  603. }
  604. var texture3D = obj as Texture3D;
  605. if (texture3D != null)
  606. {
  607. graph.owner.RegisterCompleteObjectUndo("Drag Texture 3D");
  608. var node = new SampleTexture3DNode();
  609. var drawState = node.drawState;
  610. drawState.position = new Rect(nodePosition, drawState.position.size);
  611. node.drawState = drawState;
  612. graph.AddNode(node);
  613. var inputslot = node.FindSlot<Texture3DInputMaterialSlot>(SampleTexture3DNode.TextureInputId);
  614. if (inputslot != null)
  615. inputslot.texture = texture3D;
  616. }
  617. var cubemap = obj as Cubemap;
  618. if (cubemap != null)
  619. {
  620. graph.owner.RegisterCompleteObjectUndo("Drag Cubemap");
  621. var node = new SampleCubemapNode();
  622. var drawState = node.drawState;
  623. drawState.position = new Rect(nodePosition, drawState.position.size);
  624. node.drawState = drawState;
  625. graph.AddNode(node);
  626. var inputslot = node.FindInputSlot<CubemapInputMaterialSlot>(SampleCubemapNode.CubemapInputId);
  627. if (inputslot != null)
  628. inputslot.cubemap = cubemap;
  629. }
  630. var subGraphAsset = obj as SubGraphAsset;
  631. if (subGraphAsset != null)
  632. {
  633. graph.owner.RegisterCompleteObjectUndo("Drag Sub-Graph");
  634. var node = new SubGraphNode();
  635. var drawState = node.drawState;
  636. drawState.position = new Rect(nodePosition, drawState.position.size);
  637. node.drawState = drawState;
  638. node.asset = subGraphAsset;
  639. graph.AddNode(node);
  640. }
  641. var blackboardField = obj as BlackboardField;
  642. if (blackboardField != null)
  643. {
  644. graph.owner.RegisterCompleteObjectUndo("Drag Graph Input");
  645. switch(blackboardField.userData)
  646. {
  647. case AbstractShaderProperty property:
  648. {
  649. var node = new PropertyNode();
  650. var drawState = node.drawState;
  651. drawState.position = new Rect(nodePosition, drawState.position.size);
  652. node.drawState = drawState;
  653. graph.AddNode(node);
  654. // Setting the guid requires the graph to be set first.
  655. node.propertyGuid = property.guid;
  656. break;
  657. }
  658. case ShaderKeyword keyword:
  659. {
  660. var node = new KeywordNode();
  661. var drawState = node.drawState;
  662. drawState.position = new Rect(nodePosition, drawState.position.size);
  663. node.drawState = drawState;
  664. graph.AddNode(node);
  665. // Setting the guid requires the graph to be set first.
  666. node.keywordGuid = keyword.guid;
  667. break;
  668. }
  669. default:
  670. throw new ArgumentOutOfRangeException();
  671. }
  672. }
  673. }
  674. #endregion
  675. }
  676. static class GraphViewExtensions
  677. {
  678. internal static void InsertCopyPasteGraph(this MaterialGraphView graphView, CopyPasteGraph copyGraph)
  679. {
  680. if (copyGraph == null)
  681. return;
  682. // Keywords need to be tested against variant limit based on multiple factors
  683. bool keywordsDirty = false;
  684. // Make new inputs from the copied graph
  685. foreach (ShaderInput input in copyGraph.inputs)
  686. {
  687. ShaderInput copiedInput = input.Copy();
  688. graphView.graph.SanitizeGraphInputName(copiedInput);
  689. graphView.graph.SanitizeGraphInputReferenceName(copiedInput, input.overrideReferenceName);
  690. graphView.graph.AddGraphInput(copiedInput);
  691. switch(input)
  692. {
  693. case AbstractShaderProperty property:
  694. // Update the property nodes that depends on the copied node
  695. var dependentPropertyNodes = copyGraph.GetNodes<PropertyNode>().Where(x => x.propertyGuid == input.guid);
  696. foreach (var node in dependentPropertyNodes)
  697. {
  698. node.owner = graphView.graph;
  699. node.propertyGuid = copiedInput.guid;
  700. }
  701. break;
  702. case ShaderKeyword shaderKeyword:
  703. // Update the keyword nodes that depends on the copied node
  704. var dependentKeywordNodes = copyGraph.GetNodes<KeywordNode>().Where(x => x.keywordGuid == input.guid);
  705. foreach (var node in dependentKeywordNodes)
  706. {
  707. node.owner = graphView.graph;
  708. node.keywordGuid = copiedInput.guid;
  709. }
  710. // Pasting a new Keyword so need to test against variant limit
  711. keywordsDirty = true;
  712. break;
  713. default:
  714. throw new ArgumentOutOfRangeException();
  715. }
  716. }
  717. // Pasting a Sub Graph node that contains Keywords so need to test against variant limit
  718. foreach(SubGraphNode subGraphNode in copyGraph.GetNodes<SubGraphNode>())
  719. {
  720. if(subGraphNode.asset.keywords.Count > 0)
  721. {
  722. keywordsDirty = true;
  723. }
  724. }
  725. // Test Keywords against variant limit
  726. if(keywordsDirty)
  727. {
  728. graphView.graph.OnKeywordChangedNoValidate();
  729. }
  730. using (var remappedNodesDisposable = ListPool<AbstractMaterialNode>.GetDisposable())
  731. {
  732. using (var remappedEdgesDisposable = ListPool<IEdge>.GetDisposable())
  733. {
  734. var remappedNodes = remappedNodesDisposable.value;
  735. var remappedEdges = remappedEdgesDisposable.value;
  736. graphView.graph.PasteGraph(copyGraph, remappedNodes, remappedEdges);
  737. if (graphView.graph.assetGuid != copyGraph.sourceGraphGuid)
  738. {
  739. // Compute the mean of the copied nodes.
  740. Vector2 centroid = Vector2.zero;
  741. var count = 1;
  742. foreach (var node in remappedNodes)
  743. {
  744. var position = node.drawState.position.position;
  745. centroid = centroid + (position - centroid) / count;
  746. ++count;
  747. }
  748. // Get the center of the current view
  749. var viewCenter = graphView.contentViewContainer.WorldToLocal(graphView.layout.center);
  750. foreach (var node in remappedNodes)
  751. {
  752. var drawState = node.drawState;
  753. var positionRect = drawState.position;
  754. var position = positionRect.position;
  755. position += viewCenter - centroid;
  756. positionRect.position = position;
  757. drawState.position = positionRect;
  758. node.drawState = drawState;
  759. }
  760. }
  761. // Add new elements to selection
  762. graphView.ClearSelection();
  763. graphView.graphElements.ForEach(element =>
  764. {
  765. if (element is Edge edge && remappedEdges.Contains(edge.userData as IEdge))
  766. graphView.AddToSelection(edge);
  767. if (element is IShaderNodeView nodeView && remappedNodes.Contains(nodeView.node))
  768. graphView.AddToSelection((Node)nodeView);
  769. });
  770. }
  771. }
  772. }
  773. }
  774. }