暫無描述
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

MaterialGraphView.cs 68KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reflection;
  5. using UnityEditor.Graphing.Util;
  6. using UnityEngine;
  7. using UnityEditor.Graphing;
  8. using Object = UnityEngine.Object;
  9. using UnityEditor.Experimental.GraphView;
  10. using UnityEditor.ShaderGraph.Drawing.Inspector.PropertyDrawers;
  11. using UnityEditor.ShaderGraph.Drawing.Views;
  12. using UnityEditor.ShaderGraph.Internal;
  13. using UnityEditor.ShaderGraph.Serialization;
  14. using UnityEngine.UIElements;
  15. using Edge = UnityEditor.Experimental.GraphView.Edge;
  16. using Node = UnityEditor.Experimental.GraphView.Node;
  17. using UnityEngine.Pool;
  18. namespace UnityEditor.ShaderGraph.Drawing
  19. {
  20. sealed class MaterialGraphView : GraphView, IInspectable, ISelectionProvider
  21. {
  22. readonly MethodInfo m_UndoRedoPerformedMethodInfo;
  23. public MaterialGraphView()
  24. {
  25. styleSheets.Add(Resources.Load<StyleSheet>("Styles/MaterialGraphView"));
  26. serializeGraphElements = SerializeGraphElementsImplementation;
  27. canPasteSerializedData = CanPasteSerializedDataImplementation;
  28. unserializeAndPaste = UnserializeAndPasteImplementation;
  29. deleteSelection = DeleteSelectionImplementation;
  30. elementsInsertedToStackNode = ElementsInsertedToStackNode;
  31. RegisterCallback<DragUpdatedEvent>(OnDragUpdatedEvent);
  32. RegisterCallback<DragPerformEvent>(OnDragPerformEvent);
  33. RegisterCallback<MouseMoveEvent>(OnMouseMoveEvent);
  34. this.viewTransformChanged += OnTransformChanged;
  35. // Get reference to GraphView assembly
  36. Assembly graphViewAssembly = null;
  37. foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
  38. {
  39. var assemblyName = assembly.GetName().ToString();
  40. if (assemblyName.Contains("GraphView"))
  41. {
  42. graphViewAssembly = assembly;
  43. }
  44. }
  45. Type graphViewType = graphViewAssembly?.GetType("UnityEditor.Experimental.GraphView.GraphView");
  46. // Cache the method info for this function to be used through application lifetime
  47. m_UndoRedoPerformedMethodInfo = graphViewType?.GetMethod("UndoRedoPerformed",
  48. BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.NonPublic,
  49. null,
  50. new Type[] { },
  51. null);
  52. }
  53. // GraphView has a bug where the viewTransform will be reset to default when swapping between two
  54. // GraphViewEditor windows of the same type. This is a hack to prevent that from happening w/as little
  55. // halo as possible.
  56. Vector3 lkgPosition;
  57. Vector3 lkgScale;
  58. void OnTransformChanged(GraphView graphView)
  59. {
  60. if (!graphView.viewTransform.position.Equals(Vector3.zero))
  61. {
  62. lkgPosition = graphView.viewTransform.position;
  63. lkgScale = graphView.viewTransform.scale;
  64. }
  65. else if (!lkgPosition.Equals(Vector3.zero))
  66. {
  67. graphView.UpdateViewTransform(lkgPosition, lkgScale);
  68. }
  69. }
  70. protected override bool canCutSelection
  71. {
  72. get { return selection.OfType<IShaderNodeView>().Any(x => x.node.canCutNode) || selection.OfType<Group>().Any() || selection.OfType<SGBlackboardField>().Any() || selection.OfType<SGBlackboardCategory>().Any() || selection.OfType<StickyNote>().Any(); }
  73. }
  74. protected override bool canCopySelection
  75. {
  76. get { return selection.OfType<IShaderNodeView>().Any(x => x.node.canCopyNode) || selection.OfType<Group>().Any() || selection.OfType<SGBlackboardField>().Any() || selection.OfType<SGBlackboardCategory>().Any() || selection.OfType<StickyNote>().Any(); }
  77. }
  78. public MaterialGraphView(GraphData graph, Action previewUpdateDelegate) : this()
  79. {
  80. this.graph = graph;
  81. this.m_PreviewManagerUpdateDelegate = previewUpdateDelegate;
  82. }
  83. [Inspectable("GraphData", null)]
  84. public GraphData graph { get; private set; }
  85. Action m_BlackboardFieldDropDelegate;
  86. internal Action blackboardFieldDropDelegate
  87. {
  88. get => m_BlackboardFieldDropDelegate;
  89. set => m_BlackboardFieldDropDelegate = value;
  90. }
  91. public List<ISelectable> GetSelection => selection;
  92. Action m_InspectorUpdateDelegate;
  93. Action m_PreviewManagerUpdateDelegate;
  94. public string inspectorTitle => this.graph.path;
  95. public object GetObjectToInspect()
  96. {
  97. return graph;
  98. }
  99. public void SupplyDataToPropertyDrawer(IPropertyDrawer propertyDrawer, Action inspectorUpdateDelegate)
  100. {
  101. m_InspectorUpdateDelegate = inspectorUpdateDelegate;
  102. if (propertyDrawer is GraphDataPropertyDrawer graphDataPropertyDrawer)
  103. {
  104. graphDataPropertyDrawer.GetPropertyData(this.ChangeTargetSettings, ChangePrecision);
  105. }
  106. }
  107. void ChangeTargetSettings()
  108. {
  109. var activeBlocks = graph.GetActiveBlocksForAllActiveTargets();
  110. if (ShaderGraphPreferences.autoAddRemoveBlocks)
  111. {
  112. graph.AddRemoveBlocksFromActiveList(activeBlocks);
  113. }
  114. graph.UpdateActiveBlocks(activeBlocks);
  115. this.m_PreviewManagerUpdateDelegate();
  116. this.m_InspectorUpdateDelegate();
  117. }
  118. void ChangePrecision(GraphPrecision newGraphDefaultPrecision)
  119. {
  120. if (graph.graphDefaultPrecision == newGraphDefaultPrecision)
  121. return;
  122. graph.owner.RegisterCompleteObjectUndo("Change Graph Default Precision");
  123. graph.SetGraphDefaultPrecision(newGraphDefaultPrecision);
  124. var graphEditorView = this.GetFirstAncestorOfType<GraphEditorView>();
  125. if (graphEditorView == null)
  126. return;
  127. var nodeList = this.Query<MaterialNodeView>().ToList();
  128. graphEditorView.colorManager.SetNodesDirty(nodeList);
  129. graph.ValidateGraph();
  130. graphEditorView.colorManager.UpdateNodeViews(nodeList);
  131. foreach (var node in graph.GetNodes<AbstractMaterialNode>())
  132. {
  133. node.Dirty(ModificationScope.Graph);
  134. }
  135. }
  136. public Action onConvertToSubgraphClick { get; set; }
  137. public Vector2 cachedMousePosition { get; private set; }
  138. public bool wasUndoRedoPerformed { get; set; }
  139. // GraphView has UQueryState<Node> nodes built in to query for Nodes
  140. // We need this for Contexts but we might as well cast it to a list once
  141. public List<ContextView> contexts { get; set; }
  142. // We have to manually update Contexts
  143. // Currently only called during GraphEditorView ctor as our Contexts are static
  144. public void UpdateContextList()
  145. {
  146. var contextQuery = contentViewContainer.Query<ContextView>().Build();
  147. contexts = contextQuery.ToList();
  148. }
  149. // We need a way to access specific ContextViews
  150. public ContextView GetContext(ContextData contextData)
  151. {
  152. return contexts.FirstOrDefault(s => s.contextData == contextData);
  153. }
  154. public override List<Port> GetCompatiblePorts(Port startAnchor, NodeAdapter nodeAdapter)
  155. {
  156. var compatibleAnchors = new List<Port>();
  157. var startSlot = startAnchor.GetSlot();
  158. if (startSlot == null)
  159. return compatibleAnchors;
  160. var startStage = startSlot.stageCapability;
  161. // If this is a sub-graph node we always have to check the effective stage as we might have to trace back through the sub-graph
  162. if (startStage == ShaderStageCapability.All || startSlot.owner is SubGraphNode)
  163. startStage = NodeUtils.GetEffectiveShaderStageCapability(startSlot, true) & NodeUtils.GetEffectiveShaderStageCapability(startSlot, false);
  164. foreach (var candidateAnchor in ports.ToList())
  165. {
  166. var candidateSlot = candidateAnchor.GetSlot();
  167. if (!startSlot.IsCompatibleWith(candidateSlot))
  168. continue;
  169. if (startStage != ShaderStageCapability.All)
  170. {
  171. var candidateStage = candidateSlot.stageCapability;
  172. if (candidateStage == ShaderStageCapability.All || candidateSlot.owner is SubGraphNode)
  173. candidateStage = NodeUtils.GetEffectiveShaderStageCapability(candidateSlot, true)
  174. & NodeUtils.GetEffectiveShaderStageCapability(candidateSlot, false);
  175. if (candidateStage != ShaderStageCapability.All && candidateStage != startStage)
  176. continue;
  177. // None stage can only connect to All stage, otherwise you can connect invalid connections
  178. if (startStage == ShaderStageCapability.None && candidateStage != ShaderStageCapability.All)
  179. continue;
  180. }
  181. compatibleAnchors.Add(candidateAnchor);
  182. }
  183. return compatibleAnchors;
  184. }
  185. internal bool ResetSelectedBlockNodes()
  186. {
  187. bool anyNodesWereReset = false;
  188. var selectedBlocknodes = selection.FindAll(e => e is MaterialNodeView && ((MaterialNodeView)e).node is BlockNode).Cast<MaterialNodeView>().ToArray();
  189. foreach (var mNode in selectedBlocknodes)
  190. {
  191. var bNode = mNode.node as BlockNode;
  192. var context = GetContext(bNode.contextData);
  193. // Check if the node is currently floating (it's parent isn't the context view that owns it).
  194. // If the node's not floating then the block doesn't need to be reset.
  195. bool isFloating = mNode.parent != context;
  196. if (!isFloating)
  197. continue;
  198. anyNodesWereReset = true;
  199. RemoveElement(mNode);
  200. context.InsertBlock(mNode);
  201. // TODO: StackNode in GraphView (Trunk) has no interface to reset drop previews. The least intrusive
  202. // solution is to call its DragLeave until its interface can be improved.
  203. context.DragLeave(null, null, null, null);
  204. }
  205. if (selectedBlocknodes.Length > 0)
  206. graph.ValidateCustomBlockLimit();
  207. return anyNodesWereReset;
  208. }
  209. public override void BuildContextualMenu(ContextualMenuPopulateEvent evt)
  210. {
  211. Vector2 mousePosition = evt.mousePosition;
  212. // If the target wasn't a block node, but there is one selected (and reset) by the time we reach this point,
  213. // it means a block node was in an invalid configuration and that it may be unsafe to build the context menu.
  214. bool targetIsBlockNode = evt.target is MaterialNodeView && ((MaterialNodeView)evt.target).node is BlockNode;
  215. if (ResetSelectedBlockNodes() && !targetIsBlockNode)
  216. {
  217. return;
  218. }
  219. base.BuildContextualMenu(evt);
  220. if (evt.target is GraphView)
  221. {
  222. evt.menu.InsertAction(1, "Create Sticky Note", (e) => { AddStickyNote(mousePosition); });
  223. foreach (AbstractMaterialNode node in graph.GetNodes<AbstractMaterialNode>())
  224. {
  225. var keyHint = ShaderGraphShortcuts.GetKeycodeForContextMenu(ShaderGraphShortcuts.nodePreviewShortcutID);
  226. if (node.hasPreview && node.previewExpanded == true)
  227. evt.menu.InsertAction(2, $"Collapse All Previews {keyHint}", CollapsePreviews, (a) => DropdownMenuAction.Status.Normal);
  228. if (node.hasPreview && node.previewExpanded == false)
  229. evt.menu.InsertAction(2, $"Expand All Previews {keyHint}", ExpandPreviews, (a) => DropdownMenuAction.Status.Normal);
  230. }
  231. evt.menu.AppendSeparator();
  232. }
  233. if (evt.target is GraphView || evt.target is Node)
  234. {
  235. if (evt.target is Node node)
  236. {
  237. if (!selection.Contains(node))
  238. {
  239. selection.Clear();
  240. selection.Add(node);
  241. }
  242. }
  243. evt.menu.AppendAction("Select/Unused Nodes", SelectUnusedNodes);
  244. InitializeViewSubMenu(evt);
  245. InitializePrecisionSubMenu(evt);
  246. evt.menu.AppendAction("Convert To/Sub-graph", ConvertToSubgraph, ConvertToSubgraphStatus);
  247. evt.menu.AppendAction("Convert To/Inline Node", ConvertToInlineNode, ConvertToInlineNodeStatus);
  248. evt.menu.AppendAction("Convert To/Property", ConvertToProperty, ConvertToPropertyStatus);
  249. evt.menu.AppendSeparator();
  250. var editorView = GetFirstAncestorOfType<GraphEditorView>();
  251. if (editorView.colorManager.activeSupportsCustom && selection.OfType<MaterialNodeView>().Any())
  252. {
  253. evt.menu.AppendSeparator();
  254. evt.menu.AppendAction("Color/Change...", ChangeCustomNodeColor,
  255. eventBase => DropdownMenuAction.Status.Normal);
  256. evt.menu.AppendAction("Color/Reset", menuAction =>
  257. {
  258. graph.owner.RegisterCompleteObjectUndo("Reset Node Color");
  259. foreach (var selectable in selection)
  260. {
  261. if (selectable is MaterialNodeView nodeView)
  262. {
  263. nodeView.node.ResetColor(editorView.colorManager.activeProviderName);
  264. editorView.colorManager.UpdateNodeView(nodeView);
  265. }
  266. }
  267. }, eventBase => DropdownMenuAction.Status.Normal);
  268. }
  269. if (selection.OfType<IShaderNodeView>().Count() == 1)
  270. {
  271. evt.menu.AppendSeparator();
  272. var sc = ShaderGraphShortcuts.GetKeycodeForContextMenu(ShaderGraphShortcuts.summonDocumentationShortcutID);
  273. evt.menu.AppendAction($"Open Documentation _{sc}", SeeDocumentation, SeeDocumentationStatus);
  274. }
  275. if (selection.OfType<IShaderNodeView>().Count() == 1 && selection.OfType<IShaderNodeView>().First().node is SubGraphNode)
  276. {
  277. evt.menu.AppendSeparator();
  278. evt.menu.AppendAction("Open Sub Graph", OpenSubGraph, (a) => DropdownMenuAction.Status.Normal);
  279. }
  280. }
  281. evt.menu.AppendSeparator();
  282. if (evt.target is StickyNote)
  283. {
  284. evt.menu.AppendAction("Select/Unused Nodes", SelectUnusedNodes);
  285. evt.menu.AppendSeparator();
  286. }
  287. // This needs to work on nodes, groups and properties
  288. if ((evt.target is Node) || (evt.target is StickyNote))
  289. {
  290. var scg = ShaderGraphShortcuts.GetKeycodeForContextMenu(ShaderGraphShortcuts.nodeGroupShortcutID);
  291. evt.menu.AppendAction($"Group Selection {scg}", _ => GroupSelection(), (a) =>
  292. {
  293. List<ISelectable> filteredSelection = new List<ISelectable>();
  294. foreach (ISelectable selectedObject in selection)
  295. {
  296. if (selectedObject is Group)
  297. return DropdownMenuAction.Status.Disabled;
  298. GraphElement ge = selectedObject as GraphElement;
  299. if (ge.userData is BlockNode)
  300. {
  301. return DropdownMenuAction.Status.Disabled;
  302. }
  303. if (ge.userData is IGroupItem)
  304. {
  305. filteredSelection.Add(ge);
  306. }
  307. }
  308. if (filteredSelection.Count > 0)
  309. return DropdownMenuAction.Status.Normal;
  310. return DropdownMenuAction.Status.Disabled;
  311. });
  312. var scu = ShaderGraphShortcuts.GetKeycodeForContextMenu(ShaderGraphShortcuts.nodeUnGroupShortcutID);
  313. evt.menu.AppendAction($"Ungroup Selection {scu}", _ => RemoveFromGroupNode(), (a) =>
  314. {
  315. List<ISelectable> filteredSelection = new List<ISelectable>();
  316. foreach (ISelectable selectedObject in selection)
  317. {
  318. if (selectedObject is Group)
  319. return DropdownMenuAction.Status.Disabled;
  320. GraphElement ge = selectedObject as GraphElement;
  321. if (ge.userData is IGroupItem)
  322. {
  323. if (ge.GetContainingScope() is Group)
  324. filteredSelection.Add(ge);
  325. }
  326. }
  327. if (filteredSelection.Count > 0)
  328. return DropdownMenuAction.Status.Normal;
  329. return DropdownMenuAction.Status.Disabled;
  330. });
  331. }
  332. if (evt.target is ShaderGroup shaderGroup)
  333. {
  334. evt.menu.AppendAction("Select/Unused Nodes", SelectUnusedNodes);
  335. evt.menu.AppendSeparator();
  336. if (!selection.Contains(shaderGroup))
  337. {
  338. selection.Add(shaderGroup);
  339. }
  340. var data = shaderGroup.userData;
  341. int count = evt.menu.MenuItems().Count;
  342. evt.menu.InsertAction(count, "Delete Group and Contents", (e) => RemoveNodesInsideGroup(e, data), DropdownMenuAction.AlwaysEnabled);
  343. }
  344. if (evt.target is SGBlackboardField || evt.target is SGBlackboardCategory)
  345. {
  346. evt.menu.AppendAction("Delete", (e) => DeleteSelectionImplementation("Delete", AskUser.DontAskUser), (e) => canDeleteSelection ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Disabled);
  347. evt.menu.AppendAction("Duplicate %d", (e) => DuplicateSelection(), (a) => canDuplicateSelection ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Disabled);
  348. }
  349. // Sticky notes aren't given these context menus in GraphView because it checks for specific types.
  350. // We can manually add them back in here (although the context menu ordering is different).
  351. if (evt.target is StickyNote)
  352. {
  353. evt.menu.AppendAction("Copy %d", (e) => CopySelectionCallback(), (a) => canCopySelection ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Disabled);
  354. evt.menu.AppendAction("Cut %d", (e) => CutSelectionCallback(), (a) => canCutSelection ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Disabled);
  355. evt.menu.AppendAction("Duplicate %d", (e) => DuplicateSelectionCallback(), (a) => canDuplicateSelection ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Disabled);
  356. }
  357. // Contextual menu
  358. if (evt.target is Edge)
  359. {
  360. var target = evt.target as Edge;
  361. var pos = evt.mousePosition;
  362. var keyHint = ShaderGraphShortcuts.GetKeycodeForContextMenu(ShaderGraphShortcuts.createRedirectNodeShortcutID);
  363. evt.menu.AppendSeparator();
  364. evt.menu.AppendAction($"Add Redirect Node {keyHint}", e => CreateRedirectNode(pos, target));
  365. }
  366. }
  367. public void CreateRedirectNode(Vector2 position, Edge edgeTarget)
  368. {
  369. var outputSlot = edgeTarget.output.GetSlot();
  370. var inputSlot = edgeTarget.input.GetSlot();
  371. // Need to check if the Nodes that are connected are in a group or not
  372. // If they are in the same group we also add in the Redirect Node
  373. // var groupGuidOutputNode = graph.GetNodeFromGuid(outputSlot.slotReference.nodeGuid).groupGuid;
  374. // var groupGuidInputNode = graph.GetNodeFromGuid(inputSlot.slotReference.nodeGuid).groupGuid;
  375. GroupData group = null;
  376. if (outputSlot.owner.group == inputSlot.owner.group)
  377. {
  378. group = inputSlot.owner.group;
  379. }
  380. RedirectNodeData.Create(graph, outputSlot.concreteValueType, contentViewContainer.WorldToLocal(position), inputSlot.slotReference,
  381. outputSlot.slotReference, group);
  382. }
  383. void SelectUnusedNodes(DropdownMenuAction action)
  384. {
  385. graph.owner.RegisterCompleteObjectUndo("Select Unused Nodes");
  386. ClearSelection();
  387. List<AbstractMaterialNode> endNodes = new List<AbstractMaterialNode>();
  388. if (!graph.isSubGraph)
  389. {
  390. var nodeView = graph.GetNodes<BlockNode>();
  391. foreach (BlockNode blockNode in nodeView)
  392. {
  393. endNodes.Add(blockNode as AbstractMaterialNode);
  394. }
  395. }
  396. else
  397. {
  398. var nodes = graph.GetNodes<SubGraphOutputNode>();
  399. foreach (var node in nodes)
  400. {
  401. endNodes.Add(node);
  402. }
  403. }
  404. var nodesConnectedToAMasterNode = new HashSet<AbstractMaterialNode>();
  405. // Get the list of nodes from Master nodes or SubGraphOutputNode
  406. foreach (var abs in endNodes)
  407. {
  408. NodeUtils.DepthFirstCollectNodesFromNode(nodesConnectedToAMasterNode, abs);
  409. }
  410. selection.Clear();
  411. // Get all nodes and then compare with the master nodes list
  412. var allNodes = nodes.ToList().OfType<IShaderNodeView>();
  413. foreach (IShaderNodeView materialNodeView in allNodes)
  414. {
  415. if (!nodesConnectedToAMasterNode.Contains(materialNodeView.node))
  416. {
  417. var nd = materialNodeView as GraphElement;
  418. AddToSelection(nd);
  419. }
  420. }
  421. }
  422. public delegate void SelectionChanged(List<ISelectable> selection);
  423. public SelectionChanged OnSelectionChange;
  424. public override void AddToSelection(ISelectable selectable)
  425. {
  426. base.AddToSelection(selectable);
  427. OnSelectionChange?.Invoke(selection);
  428. }
  429. // Replicating these private GraphView functions as we need them for our own purposes
  430. internal void AddToSelectionNoUndoRecord(GraphElement graphElement)
  431. {
  432. graphElement.selected = true;
  433. selection.Add(graphElement);
  434. graphElement.OnSelected();
  435. OnSelectionChange?.Invoke(selection);
  436. // To ensure that the selected GraphElement gets unselected if it is removed from the GraphView.
  437. graphElement.RegisterCallback<DetachFromPanelEvent>(OnSelectedElementDetachedFromPanel);
  438. graphElement.MarkDirtyRepaint();
  439. }
  440. public override void RemoveFromSelection(ISelectable selectable)
  441. {
  442. base.RemoveFromSelection(selectable);
  443. if (OnSelectionChange != null)
  444. OnSelectionChange(selection);
  445. }
  446. internal void RemoveFromSelectionNoUndoRecord(ISelectable selectable)
  447. {
  448. var graphElement = selectable as GraphElement;
  449. if (graphElement == null)
  450. return;
  451. graphElement.selected = false;
  452. OnSelectionChange?.Invoke(selection);
  453. selection.Remove(selectable);
  454. graphElement.OnUnselected();
  455. graphElement.UnregisterCallback<DetachFromPanelEvent>(OnSelectedElementDetachedFromPanel);
  456. graphElement.MarkDirtyRepaint();
  457. }
  458. private void OnSelectedElementDetachedFromPanel(DetachFromPanelEvent evt)
  459. {
  460. RemoveFromSelectionNoUndoRecord(evt.target as ISelectable);
  461. }
  462. public override void ClearSelection()
  463. {
  464. base.ClearSelection();
  465. OnSelectionChange?.Invoke(selection);
  466. }
  467. internal bool ClearSelectionNoUndoRecord()
  468. {
  469. foreach (var graphElement in selection.OfType<GraphElement>())
  470. {
  471. graphElement.selected = false;
  472. graphElement.OnUnselected();
  473. graphElement.UnregisterCallback<DetachFromPanelEvent>(OnSelectedElementDetachedFromPanel);
  474. graphElement.MarkDirtyRepaint();
  475. }
  476. OnSelectionChange?.Invoke(selection);
  477. bool selectionWasNotEmpty = selection.Any();
  478. selection.Clear();
  479. return selectionWasNotEmpty;
  480. }
  481. private void RemoveNodesInsideGroup(DropdownMenuAction action, GroupData data)
  482. {
  483. graph.owner.RegisterCompleteObjectUndo("Delete Group and Contents");
  484. var groupItems = graph.GetItemsInGroup(data);
  485. graph.RemoveElements(groupItems.OfType<AbstractMaterialNode>().ToArray(), new IEdge[] { }, new[] { data }, groupItems.OfType<StickyNoteData>().ToArray());
  486. }
  487. private void InitializePrecisionSubMenu(ContextualMenuPopulateEvent evt)
  488. {
  489. // Default the menu buttons to disabled
  490. DropdownMenuAction.Status inheritPrecisionAction = DropdownMenuAction.Status.Disabled;
  491. DropdownMenuAction.Status floatPrecisionAction = DropdownMenuAction.Status.Disabled;
  492. DropdownMenuAction.Status halfPrecisionAction = DropdownMenuAction.Status.Disabled;
  493. // Check which precisions are available to switch to
  494. foreach (MaterialNodeView selectedNode in selection.Where(x => x is MaterialNodeView).Select(x => x as MaterialNodeView))
  495. {
  496. if (selectedNode.node.precision != Precision.Inherit)
  497. inheritPrecisionAction = DropdownMenuAction.Status.Normal;
  498. if (selectedNode.node.precision != Precision.Single)
  499. floatPrecisionAction = DropdownMenuAction.Status.Normal;
  500. if (selectedNode.node.precision != Precision.Half)
  501. halfPrecisionAction = DropdownMenuAction.Status.Normal;
  502. }
  503. // Create the menu options
  504. evt.menu.AppendAction("Precision/Inherit", _ => SetNodePrecisionOnSelection(Precision.Inherit), (a) => inheritPrecisionAction);
  505. evt.menu.AppendAction("Precision/Single", _ => SetNodePrecisionOnSelection(Precision.Single), (a) => floatPrecisionAction);
  506. evt.menu.AppendAction("Precision/Half", _ => SetNodePrecisionOnSelection(Precision.Half), (a) => halfPrecisionAction);
  507. }
  508. private void InitializeViewSubMenu(ContextualMenuPopulateEvent evt)
  509. {
  510. // Default the menu buttons to disabled
  511. DropdownMenuAction.Status expandPreviewAction = DropdownMenuAction.Status.Disabled;
  512. DropdownMenuAction.Status collapsePreviewAction = DropdownMenuAction.Status.Disabled;
  513. DropdownMenuAction.Status minimizeAction = DropdownMenuAction.Status.Disabled;
  514. DropdownMenuAction.Status maximizeAction = DropdownMenuAction.Status.Disabled;
  515. // Initialize strings
  516. var previewKeyHint = ShaderGraphShortcuts.GetKeycodeForContextMenu(ShaderGraphShortcuts.nodePreviewShortcutID);
  517. var portKeyHint = ShaderGraphShortcuts.GetKeycodeForContextMenu(ShaderGraphShortcuts.nodeCollapsedShortcutID);
  518. string expandPreviewText = $"View/Expand Previews {previewKeyHint}";
  519. string collapsePreviewText = $"View/Collapse Previews {previewKeyHint}";
  520. string expandPortText = $"View/Expand Ports {portKeyHint}";
  521. string collapsePortText = $"View/Collapse Ports {portKeyHint}";
  522. if (selection.Count == 1)
  523. {
  524. collapsePreviewText = $"View/Collapse Preview {previewKeyHint}";
  525. expandPreviewText = $"View/Expand Preview {previewKeyHint}";
  526. }
  527. // Check if we can expand or collapse the ports/previews
  528. foreach (MaterialNodeView selectedNode in selection.Where(x => x is MaterialNodeView).Select(x => x as MaterialNodeView))
  529. {
  530. if (selectedNode.node.hasPreview)
  531. {
  532. if (selectedNode.node.previewExpanded)
  533. collapsePreviewAction = DropdownMenuAction.Status.Normal;
  534. else
  535. expandPreviewAction = DropdownMenuAction.Status.Normal;
  536. }
  537. if (selectedNode.CanToggleNodeExpanded())
  538. {
  539. if (selectedNode.expanded)
  540. minimizeAction = DropdownMenuAction.Status.Normal;
  541. else
  542. maximizeAction = DropdownMenuAction.Status.Normal;
  543. }
  544. }
  545. // Create the menu options
  546. evt.menu.AppendAction(collapsePortText, _ => SetNodeExpandedForSelectedNodes(false), (a) => minimizeAction);
  547. evt.menu.AppendAction(expandPortText, _ => SetNodeExpandedForSelectedNodes(true), (a) => maximizeAction);
  548. evt.menu.AppendSeparator("View/");
  549. evt.menu.AppendAction(expandPreviewText, _ => SetPreviewExpandedForSelectedNodes(true), (a) => expandPreviewAction);
  550. evt.menu.AppendAction(collapsePreviewText, _ => SetPreviewExpandedForSelectedNodes(false), (a) => collapsePreviewAction);
  551. }
  552. void ChangeCustomNodeColor(DropdownMenuAction menuAction)
  553. {
  554. // Color Picker is internal :(
  555. var t = typeof(EditorWindow).Assembly.GetTypes().FirstOrDefault(ty => ty.Name == "ColorPicker");
  556. var m = t?.GetMethod("Show", new[] { typeof(Action<Color>), typeof(Color), typeof(bool), typeof(bool) });
  557. if (m == null)
  558. {
  559. Debug.LogWarning("Could not invoke Color Picker for ShaderGraph.");
  560. return;
  561. }
  562. var editorView = GetFirstAncestorOfType<GraphEditorView>();
  563. var defaultColor = Color.gray;
  564. if (selection.FirstOrDefault(sel => sel is MaterialNodeView) is MaterialNodeView selNode1)
  565. {
  566. defaultColor = selNode1.GetColor();
  567. defaultColor.a = 1.0f;
  568. }
  569. void ApplyColor(Color pickedColor)
  570. {
  571. foreach (var selectable in selection)
  572. {
  573. if (selectable is MaterialNodeView nodeView)
  574. {
  575. nodeView.node.SetColor(editorView.colorManager.activeProviderName, pickedColor);
  576. editorView.colorManager.UpdateNodeView(nodeView);
  577. }
  578. }
  579. }
  580. graph.owner.RegisterCompleteObjectUndo("Change Node Color");
  581. m.Invoke(null, new object[] { (Action<Color>)ApplyColor, defaultColor, true, false });
  582. }
  583. protected override bool canDeleteSelection
  584. {
  585. get
  586. {
  587. return selection.Any(x => !(x is IShaderNodeView nodeView) || nodeView.node.canDeleteNode);
  588. }
  589. }
  590. public void GroupSelection()
  591. {
  592. var title = "New Group";
  593. var groupData = new GroupData(title, new Vector2(10f, 10f));
  594. graph.owner.RegisterCompleteObjectUndo("Create Group Node");
  595. graph.CreateGroup(groupData);
  596. foreach (var element in selection.OfType<GraphElement>())
  597. {
  598. if (element.userData is IGroupItem groupItem)
  599. {
  600. graph.SetGroup(groupItem, groupData);
  601. }
  602. }
  603. }
  604. public void AddStickyNote(Vector2 position)
  605. {
  606. position = contentViewContainer.WorldToLocal(position);
  607. string title = "New Note";
  608. string content = "Write something here";
  609. var stickyNoteData = new StickyNoteData(title, content, new Rect(position.x, position.y, 200, 160));
  610. graph.owner.RegisterCompleteObjectUndo("Create Sticky Note");
  611. graph.AddStickyNote(stickyNoteData);
  612. }
  613. public void RemoveFromGroupNode()
  614. {
  615. graph.owner.RegisterCompleteObjectUndo("Ungroup Node(s)");
  616. foreach (var element in selection.OfType<GraphElement>())
  617. {
  618. if (element.userData is IGroupItem)
  619. {
  620. Group group = element.GetContainingScope() as Group;
  621. if (group != null)
  622. {
  623. group.RemoveElement(element);
  624. }
  625. }
  626. }
  627. }
  628. public void SetNodeExpandedForSelectedNodes(bool state, bool recordUndo = true)
  629. {
  630. if (recordUndo)
  631. {
  632. graph.owner.RegisterCompleteObjectUndo(state ? "Expand Nodes" : "Collapse Nodes");
  633. }
  634. foreach (MaterialNodeView selectedNode in selection.Where(x => x is MaterialNodeView).Select(x => x as MaterialNodeView))
  635. {
  636. if (selectedNode.CanToggleNodeExpanded() && selectedNode.expanded != state)
  637. {
  638. selectedNode.expanded = state;
  639. selectedNode.node.Dirty(ModificationScope.Topological);
  640. }
  641. }
  642. }
  643. public void SetPreviewExpandedForSelectedNodes(bool state)
  644. {
  645. graph.owner.RegisterCompleteObjectUndo(state ? "Expand Nodes" : "Collapse Nodes");
  646. foreach (MaterialNodeView selectedNode in selection.Where(x => x is MaterialNodeView).Select(x => x as MaterialNodeView))
  647. {
  648. selectedNode.node.previewExpanded = state;
  649. }
  650. }
  651. public void SetNodePrecisionOnSelection(Precision inPrecision)
  652. {
  653. var editorView = GetFirstAncestorOfType<GraphEditorView>();
  654. IEnumerable<MaterialNodeView> nodes = selection.Where(x => x is MaterialNodeView node && node.node.canSetPrecision).Select(x => x as MaterialNodeView);
  655. graph.owner.RegisterCompleteObjectUndo("Set Precisions");
  656. editorView.colorManager.SetNodesDirty(nodes);
  657. foreach (MaterialNodeView selectedNode in nodes)
  658. {
  659. selectedNode.node.precision = inPrecision;
  660. }
  661. // Reflect the data down
  662. graph.ValidateGraph();
  663. editorView.colorManager.UpdateNodeViews(nodes);
  664. // Update the views
  665. foreach (MaterialNodeView selectedNode in nodes)
  666. selectedNode.node.Dirty(ModificationScope.Topological);
  667. }
  668. void CollapsePreviews(DropdownMenuAction action)
  669. {
  670. graph.owner.RegisterCompleteObjectUndo("Collapse Previews");
  671. foreach (AbstractMaterialNode node in graph.GetNodes<AbstractMaterialNode>())
  672. {
  673. node.previewExpanded = false;
  674. }
  675. }
  676. void ExpandPreviews(DropdownMenuAction action)
  677. {
  678. graph.owner.RegisterCompleteObjectUndo("Expand Previews");
  679. foreach (AbstractMaterialNode node in graph.GetNodes<AbstractMaterialNode>())
  680. {
  681. node.previewExpanded = true;
  682. }
  683. }
  684. void SeeDocumentation(DropdownMenuAction action)
  685. {
  686. var node = selection.OfType<IShaderNodeView>().First().node;
  687. if (node.documentationURL != null)
  688. System.Diagnostics.Process.Start(node.documentationURL);
  689. }
  690. void OpenSubGraph(DropdownMenuAction action)
  691. {
  692. SubGraphNode subgraphNode = selection.OfType<IShaderNodeView>().First().node as SubGraphNode;
  693. var path = AssetDatabase.GetAssetPath(subgraphNode.asset);
  694. ShaderGraphImporterEditor.ShowGraphEditWindow(path);
  695. }
  696. DropdownMenuAction.Status SeeDocumentationStatus(DropdownMenuAction action)
  697. {
  698. if (selection.OfType<IShaderNodeView>().First().node.documentationURL == null)
  699. return DropdownMenuAction.Status.Disabled;
  700. return DropdownMenuAction.Status.Normal;
  701. }
  702. DropdownMenuAction.Status ConvertToPropertyStatus(DropdownMenuAction action)
  703. {
  704. if (selection.OfType<IShaderNodeView>().Any(v => v.node != null))
  705. {
  706. if (selection.OfType<IShaderNodeView>().Any(v => v.node is IPropertyFromNode))
  707. return DropdownMenuAction.Status.Normal;
  708. return DropdownMenuAction.Status.Disabled;
  709. }
  710. return DropdownMenuAction.Status.Hidden;
  711. }
  712. void ConvertToProperty(DropdownMenuAction action)
  713. {
  714. var convertToPropertyAction = new ConvertToPropertyAction();
  715. var selectedNodeViews = selection.OfType<IShaderNodeView>().Select(x => x.node).ToList();
  716. foreach (var node in selectedNodeViews)
  717. {
  718. if (!(node is IPropertyFromNode))
  719. continue;
  720. var converter = node as IPropertyFromNode;
  721. convertToPropertyAction.inlinePropertiesToConvert.Add(converter);
  722. }
  723. graph.owner.graphDataStore.Dispatch(convertToPropertyAction);
  724. }
  725. DropdownMenuAction.Status ConvertToInlineNodeStatus(DropdownMenuAction action)
  726. {
  727. if (selection.OfType<IShaderNodeView>().Any(v => v.node != null))
  728. {
  729. if (selection.OfType<IShaderNodeView>().Any(v => v.node is PropertyNode))
  730. return DropdownMenuAction.Status.Normal;
  731. return DropdownMenuAction.Status.Disabled;
  732. }
  733. return DropdownMenuAction.Status.Hidden;
  734. }
  735. void ConvertToInlineNode(DropdownMenuAction action)
  736. {
  737. var selectedNodeViews = selection.OfType<IShaderNodeView>()
  738. .Select(x => x.node)
  739. .OfType<PropertyNode>();
  740. var convertToInlineAction = new ConvertToInlineAction();
  741. convertToInlineAction.propertyNodesToConvert = selectedNodeViews;
  742. graph.owner.graphDataStore.Dispatch(convertToInlineAction);
  743. }
  744. // Made internal for purposes of UI testing
  745. internal void DuplicateSelection()
  746. {
  747. graph.owner.RegisterCompleteObjectUndo("Duplicate Blackboard Selection");
  748. List<ShaderInput> selectedProperties = new List<ShaderInput>();
  749. List<CategoryData> selectedCategories = new List<CategoryData>();
  750. for (int index = 0; index < selection.Count; ++index)
  751. {
  752. var selectable = selection[index];
  753. if (selectable is SGBlackboardCategory blackboardCategory)
  754. {
  755. selectedCategories.Add(blackboardCategory.controller.Model);
  756. var childBlackboardFields = blackboardCategory.Query<SGBlackboardField>().ToList();
  757. // Remove the children that live in this category (if any) from the selection, as they will get copied twice otherwise
  758. selection.RemoveAll(childItem => childBlackboardFields.Contains(childItem));
  759. }
  760. }
  761. foreach (var selectable in selection)
  762. {
  763. if (selectable is SGBlackboardField blackboardField)
  764. {
  765. selectedProperties.Add(blackboardField.controller.Model);
  766. }
  767. }
  768. // Sort so that the ShaderInputs are in the correct order
  769. selectedProperties.Sort((x, y) => graph.GetGraphInputIndex(x) > graph.GetGraphInputIndex(y) ? 1 : -1);
  770. CopyPasteGraph copiedItems = new CopyPasteGraph(null, null, null, selectedProperties, selectedCategories, null, null, null, null, copyPasteGraphSource: CopyPasteGraphSource.Duplicate);
  771. GraphViewExtensions.InsertCopyPasteGraph(this, copiedItems);
  772. }
  773. DropdownMenuAction.Status ConvertToSubgraphStatus(DropdownMenuAction action)
  774. {
  775. if (onConvertToSubgraphClick == null) return DropdownMenuAction.Status.Hidden;
  776. return selection.OfType<IShaderNodeView>().Any(v => v.node != null && v.node.allowedInSubGraph && !(v.node is SubGraphOutputNode)) ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Hidden;
  777. }
  778. void ConvertToSubgraph(DropdownMenuAction action)
  779. {
  780. onConvertToSubgraphClick();
  781. }
  782. string SerializeGraphElementsImplementation(IEnumerable<GraphElement> elements)
  783. {
  784. var groups = elements.OfType<ShaderGroup>().Select(x => x.userData);
  785. var nodes = elements.OfType<IShaderNodeView>().Select(x => x.node).Where(x => x.canCopyNode);
  786. var edges = elements.OfType<Edge>().Select(x => (Graphing.Edge)x.userData);
  787. var notes = elements.OfType<StickyNote>().Select(x => x.userData);
  788. var categories = new List<CategoryData>();
  789. foreach (var selectable in selection)
  790. {
  791. if (selectable is SGBlackboardCategory blackboardCategory)
  792. {
  793. categories.Add(blackboardCategory.userData as CategoryData);
  794. }
  795. }
  796. var inputs = selection.OfType<SGBlackboardField>().Select(x => x.userData as ShaderInput).ToList();
  797. // Collect the property nodes and get the corresponding properties
  798. var metaProperties = new HashSet<AbstractShaderProperty>(nodes.OfType<PropertyNode>().Select(x => x.property).Concat(inputs.OfType<AbstractShaderProperty>()));
  799. // Collect the keyword nodes and get the corresponding keywords
  800. var metaKeywords = new HashSet<ShaderKeyword>(nodes.OfType<KeywordNode>().Select(x => x.keyword).Concat(inputs.OfType<ShaderKeyword>()));
  801. // Collect the dropdown nodes and get the corresponding dropdowns
  802. var metaDropdowns = new HashSet<ShaderDropdown>(nodes.OfType<DropdownNode>().Select(x => x.dropdown).Concat(inputs.OfType<ShaderDropdown>()));
  803. // Sort so that the ShaderInputs are in the correct order
  804. inputs.Sort((x, y) => graph.GetGraphInputIndex(x) > graph.GetGraphInputIndex(y) ? 1 : -1);
  805. var copyPasteGraph = new CopyPasteGraph(groups, nodes, edges, inputs, categories, metaProperties, metaKeywords, metaDropdowns, notes);
  806. return MultiJson.Serialize(copyPasteGraph);
  807. }
  808. bool CanPasteSerializedDataImplementation(string serializedData)
  809. {
  810. return CopyPasteGraph.FromJson(serializedData, graph) != null;
  811. }
  812. void UnserializeAndPasteImplementation(string operationName, string serializedData)
  813. {
  814. graph.owner.RegisterCompleteObjectUndo(operationName);
  815. var pastedGraph = CopyPasteGraph.FromJson(serializedData, graph);
  816. this.InsertCopyPasteGraph(pastedGraph);
  817. }
  818. void DeleteSelectionImplementation(string operationName, GraphView.AskUser askUser)
  819. {
  820. // Selection state of Graph elements and the Focus state of UIElements are not mutually exclusive.
  821. // For Hotkeys, askUser should be AskUser mode, which should early out so that the focused Element can win.
  822. if (this.focusController.focusedElement != null
  823. && focusController.focusedElement is UIElements.ObjectField
  824. && askUser == GraphView.AskUser.AskUser)
  825. {
  826. return;
  827. }
  828. bool containsProperty = false;
  829. // Keywords need to be tested against variant limit based on multiple factors
  830. bool keywordsDirty = false;
  831. bool dropdownsDirty = false;
  832. // Track dependent keyword nodes to remove them
  833. List<KeywordNode> keywordNodes = new List<KeywordNode>();
  834. List<DropdownNode> dropdownNodes = new List<DropdownNode>();
  835. foreach (var selectable in selection)
  836. {
  837. if (selectable is SGBlackboardField propertyView && propertyView.userData != null)
  838. {
  839. switch (propertyView.userData)
  840. {
  841. case AbstractShaderProperty property:
  842. containsProperty = true;
  843. break;
  844. case ShaderKeyword keyword:
  845. keywordNodes.AddRange(graph.GetNodes<KeywordNode>().Where(x => x.keyword == keyword));
  846. break;
  847. case ShaderDropdown dropdown:
  848. dropdownNodes.AddRange(graph.GetNodes<DropdownNode>().Where(x => x.dropdown == dropdown));
  849. break;
  850. default:
  851. throw new ArgumentOutOfRangeException();
  852. }
  853. }
  854. }
  855. if (containsProperty)
  856. {
  857. if (graph.isSubGraph)
  858. {
  859. 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"))
  860. return;
  861. }
  862. }
  863. // Filter nodes that cannot be deleted
  864. var nodesToDelete = selection.OfType<IShaderNodeView>().Where(v => !(v.node is SubGraphOutputNode) && v.node.canDeleteNode).Select(x => x.node);
  865. // Add keyword nodes dependent on deleted keywords
  866. nodesToDelete = nodesToDelete.Union(keywordNodes);
  867. nodesToDelete = nodesToDelete.Union(dropdownNodes);
  868. // If deleting a Sub Graph node whose asset contains Keywords test variant limit
  869. foreach (SubGraphNode subGraphNode in nodesToDelete.OfType<SubGraphNode>())
  870. {
  871. if (subGraphNode.asset == null)
  872. {
  873. continue;
  874. }
  875. if (subGraphNode.asset.keywords.Any())
  876. {
  877. keywordsDirty = true;
  878. }
  879. if (subGraphNode.asset.dropdowns.Any())
  880. {
  881. dropdownsDirty = true;
  882. }
  883. }
  884. graph.owner.RegisterCompleteObjectUndo(operationName);
  885. graph.RemoveElements(nodesToDelete.ToArray(),
  886. selection.OfType<Edge>().Select(x => x.userData).OfType<IEdge>().ToArray(),
  887. selection.OfType<ShaderGroup>().Select(x => x.userData).ToArray(),
  888. selection.OfType<StickyNote>().Select(x => x.userData).ToArray());
  889. var copiedSelectionList = new List<ISelectable>(selection);
  890. var deleteShaderInputAction = new DeleteShaderInputAction();
  891. var deleteCategoriesAction = new DeleteCategoryAction();
  892. for (int index = 0; index < copiedSelectionList.Count; ++index)
  893. {
  894. var selectable = copiedSelectionList[index];
  895. if (selectable is SGBlackboardField field && field.userData != null)
  896. {
  897. var input = (ShaderInput)field.userData;
  898. deleteShaderInputAction.shaderInputsToDelete.Add(input);
  899. // If deleting a Keyword test variant limit
  900. if (input is ShaderKeyword keyword)
  901. {
  902. keywordsDirty = true;
  903. }
  904. if (input is ShaderDropdown dropdown)
  905. {
  906. dropdownsDirty = true;
  907. }
  908. }
  909. // Don't allow the default category to be deleted
  910. else if (selectable is SGBlackboardCategory category && category.controller.Model.IsNamedCategory())
  911. {
  912. deleteCategoriesAction.categoriesToRemoveGuids.Add(category.viewModel.associatedCategoryGuid);
  913. }
  914. }
  915. if (deleteShaderInputAction.shaderInputsToDelete.Count != 0)
  916. graph.owner.graphDataStore.Dispatch(deleteShaderInputAction);
  917. if (deleteCategoriesAction.categoriesToRemoveGuids.Count != 0)
  918. graph.owner.graphDataStore.Dispatch(deleteCategoriesAction);
  919. // Test Keywords against variant limit
  920. if (keywordsDirty)
  921. {
  922. graph.OnKeywordChangedNoValidate();
  923. }
  924. if (dropdownsDirty)
  925. {
  926. graph.OnDropdownChangedNoValidate();
  927. }
  928. selection.Clear();
  929. m_InspectorUpdateDelegate?.Invoke();
  930. }
  931. // Updates selected graph elements after undo/redo
  932. internal void RestorePersistentSelectionAfterUndoRedo()
  933. {
  934. wasUndoRedoPerformed = true;
  935. m_UndoRedoPerformedMethodInfo?.Invoke(this, new object[] { });
  936. }
  937. #region Drag and drop
  938. bool ValidateObjectForDrop(Object obj)
  939. {
  940. return EditorUtility.IsPersistent(obj) && (
  941. obj is Texture2D ||
  942. obj is Cubemap ||
  943. obj is SubGraphAsset asset && !asset.descendents.Contains(graph.assetGuid) && asset.assetGuid != graph.assetGuid ||
  944. obj is Texture2DArray ||
  945. obj is Texture3D);
  946. }
  947. void OnDragUpdatedEvent(DragUpdatedEvent e)
  948. {
  949. var selection = DragAndDrop.GetGenericData("DragSelection") as List<ISelectable>;
  950. bool dragging = false;
  951. if (selection != null)
  952. {
  953. var anyCategoriesInSelection = selection.OfType<SGBlackboardCategory>();
  954. if (!anyCategoriesInSelection.Any())
  955. {
  956. // Blackboard items
  957. bool validFields = false;
  958. foreach (SGBlackboardField propertyView in selection.OfType<SGBlackboardField>())
  959. {
  960. if (!(propertyView.userData is MultiJsonInternal.UnknownShaderPropertyType))
  961. validFields = true;
  962. }
  963. dragging = validFields;
  964. }
  965. else
  966. DragAndDrop.visualMode = DragAndDropVisualMode.Rejected;
  967. }
  968. else
  969. {
  970. // Handle unity objects
  971. var objects = DragAndDrop.objectReferences;
  972. foreach (Object obj in objects)
  973. {
  974. if (ValidateObjectForDrop(obj))
  975. {
  976. dragging = true;
  977. break;
  978. }
  979. }
  980. }
  981. if (dragging)
  982. {
  983. DragAndDrop.visualMode = DragAndDropVisualMode.Generic;
  984. }
  985. }
  986. // Contrary to the name this actually handles when the drop operation is performed
  987. void OnDragPerformEvent(DragPerformEvent e)
  988. {
  989. Vector2 localPos = (e.currentTarget as VisualElement).ChangeCoordinatesTo(contentViewContainer, e.localMousePosition);
  990. var selection = DragAndDrop.GetGenericData("DragSelection") as List<ISelectable>;
  991. if (selection != null)
  992. {
  993. // Blackboard
  994. if (selection.OfType<SGBlackboardField>().Any())
  995. {
  996. IEnumerable<SGBlackboardField> fields = selection.OfType<SGBlackboardField>();
  997. foreach (SGBlackboardField field in fields)
  998. {
  999. CreateNode(field, localPos);
  1000. }
  1001. // Call this delegate so blackboard can respond to blackboard field being dropped
  1002. blackboardFieldDropDelegate?.Invoke();
  1003. }
  1004. }
  1005. else
  1006. {
  1007. // Handle unity objects
  1008. var objects = DragAndDrop.objectReferences;
  1009. foreach (Object obj in objects)
  1010. {
  1011. if (ValidateObjectForDrop(obj))
  1012. {
  1013. CreateNode(obj, localPos);
  1014. }
  1015. }
  1016. }
  1017. }
  1018. void OnMouseMoveEvent(MouseMoveEvent evt)
  1019. {
  1020. this.cachedMousePosition = evt.mousePosition;
  1021. }
  1022. void CreateNode(object obj, Vector2 nodePosition)
  1023. {
  1024. var texture2D = obj as Texture2D;
  1025. if (texture2D != null)
  1026. {
  1027. graph.owner.RegisterCompleteObjectUndo("Drag Texture");
  1028. bool isNormalMap = false;
  1029. if (EditorUtility.IsPersistent(texture2D) && !string.IsNullOrEmpty(AssetDatabase.GetAssetPath(texture2D)))
  1030. {
  1031. var importer = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(texture2D)) as TextureImporter;
  1032. if (importer != null)
  1033. isNormalMap = importer.textureType == TextureImporterType.NormalMap;
  1034. }
  1035. var node = new SampleTexture2DNode();
  1036. var drawState = node.drawState;
  1037. drawState.position = new Rect(nodePosition, drawState.position.size);
  1038. node.drawState = drawState;
  1039. graph.AddNode(node);
  1040. if (isNormalMap)
  1041. node.textureType = TextureType.Normal;
  1042. var inputslot = node.FindInputSlot<Texture2DInputMaterialSlot>(SampleTexture2DNode.TextureInputId);
  1043. if (inputslot != null)
  1044. inputslot.texture = texture2D;
  1045. }
  1046. var textureArray = obj as Texture2DArray;
  1047. if (textureArray != null)
  1048. {
  1049. graph.owner.RegisterCompleteObjectUndo("Drag Texture Array");
  1050. var node = new SampleTexture2DArrayNode();
  1051. var drawState = node.drawState;
  1052. drawState.position = new Rect(nodePosition, drawState.position.size);
  1053. node.drawState = drawState;
  1054. graph.AddNode(node);
  1055. var inputslot = node.FindSlot<Texture2DArrayInputMaterialSlot>(SampleTexture2DArrayNode.TextureInputId);
  1056. if (inputslot != null)
  1057. inputslot.textureArray = textureArray;
  1058. }
  1059. var texture3D = obj as Texture3D;
  1060. if (texture3D != null)
  1061. {
  1062. graph.owner.RegisterCompleteObjectUndo("Drag Texture 3D");
  1063. var node = new SampleTexture3DNode();
  1064. var drawState = node.drawState;
  1065. drawState.position = new Rect(nodePosition, drawState.position.size);
  1066. node.drawState = drawState;
  1067. graph.AddNode(node);
  1068. var inputslot = node.FindSlot<Texture3DInputMaterialSlot>(SampleTexture3DNode.TextureInputId);
  1069. if (inputslot != null)
  1070. inputslot.texture = texture3D;
  1071. }
  1072. var cubemap = obj as Cubemap;
  1073. if (cubemap != null)
  1074. {
  1075. graph.owner.RegisterCompleteObjectUndo("Drag Cubemap");
  1076. var node = new SampleCubemapNode();
  1077. var drawState = node.drawState;
  1078. drawState.position = new Rect(nodePosition, drawState.position.size);
  1079. node.drawState = drawState;
  1080. graph.AddNode(node);
  1081. var inputslot = node.FindInputSlot<CubemapInputMaterialSlot>(SampleCubemapNode.CubemapInputId);
  1082. if (inputslot != null)
  1083. inputslot.cubemap = cubemap;
  1084. }
  1085. var subGraphAsset = obj as SubGraphAsset;
  1086. if (subGraphAsset != null)
  1087. {
  1088. graph.owner.RegisterCompleteObjectUndo("Drag Sub-Graph");
  1089. var node = new SubGraphNode();
  1090. var drawState = node.drawState;
  1091. drawState.position = new Rect(nodePosition, drawState.position.size);
  1092. node.drawState = drawState;
  1093. node.asset = subGraphAsset;
  1094. graph.AddNode(node);
  1095. }
  1096. var blackboardPropertyView = obj as SGBlackboardField;
  1097. if (blackboardPropertyView?.userData is ShaderInput inputBeingDraggedIn)
  1098. {
  1099. var dragGraphInputAction = new DragGraphInputAction { nodePosition = nodePosition, graphInputBeingDraggedIn = inputBeingDraggedIn };
  1100. graph.owner.graphDataStore.Dispatch(dragGraphInputAction);
  1101. }
  1102. }
  1103. #endregion
  1104. void ElementsInsertedToStackNode(StackNode stackNode, int insertIndex, IEnumerable<GraphElement> elements)
  1105. {
  1106. var contextView = stackNode as ContextView;
  1107. contextView.InsertElements(insertIndex, elements);
  1108. }
  1109. }
  1110. static class GraphViewExtensions
  1111. {
  1112. // Sorts based on their position on the blackboard
  1113. internal class PropertyOrder : IComparer<ShaderInput>
  1114. {
  1115. GraphData graphData;
  1116. internal PropertyOrder(GraphData data)
  1117. {
  1118. graphData = data;
  1119. }
  1120. public int Compare(ShaderInput x, ShaderInput y)
  1121. {
  1122. if (graphData.GetGraphInputIndex(x) > graphData.GetGraphInputIndex(y)) return 1;
  1123. else return -1;
  1124. }
  1125. }
  1126. internal static void InsertCopyPasteGraph(this MaterialGraphView graphView, CopyPasteGraph copyGraph)
  1127. {
  1128. if (copyGraph == null)
  1129. return;
  1130. // Keywords need to be tested against variant limit based on multiple factors
  1131. bool keywordsDirty = false;
  1132. bool dropdownsDirty = false;
  1133. var blackboardController = graphView.GetFirstAncestorOfType<GraphEditorView>().blackboardController;
  1134. // Get the position to insert the new shader inputs per category
  1135. int insertionIndex = blackboardController.GetInsertionIndexForPaste();
  1136. // Any child of the categories need to be removed from selection as well (there's a Graphview issue where these don't get properly added to selection before the duplication sometimes, have to do it manually)
  1137. foreach (var selectable in graphView.selection)
  1138. {
  1139. if (selectable is SGBlackboardCategory blackboardCategory)
  1140. {
  1141. foreach (var blackboardChild in blackboardCategory.Children())
  1142. {
  1143. if (blackboardChild is SGBlackboardRow blackboardRow)
  1144. {
  1145. var blackboardField = blackboardRow.Q<SGBlackboardField>();
  1146. if (blackboardField != null)
  1147. {
  1148. blackboardField.selected = false;
  1149. blackboardField.OnUnselected();
  1150. }
  1151. }
  1152. }
  1153. }
  1154. }
  1155. var cachedSelection = graphView.selection.ToList();
  1156. // Before copy-pasting, clear all current selections so the duplicated items may be selected instead
  1157. graphView.ClearSelectionNoUndoRecord();
  1158. // Make new inputs from the copied graph
  1159. foreach (ShaderInput input in copyGraph.inputs)
  1160. {
  1161. // Disregard any inputs that belong to a category in the CopyPasteGraph already,
  1162. // GraphData handles copying those child inputs over when the category is copied
  1163. if (copyGraph.IsInputCategorized(input))
  1164. continue;
  1165. string associatedCategoryGuid = String.Empty;
  1166. foreach (var category in graphView.graph.categories)
  1167. {
  1168. if (copyGraph.IsInputDuplicatedFromCategory(input, category, graphView.graph))
  1169. {
  1170. associatedCategoryGuid = category.categoryGuid;
  1171. }
  1172. }
  1173. // In the specific case of just an input being selected to copy but some other category than the one containing it was selected, we want to copy it to the default category
  1174. if (associatedCategoryGuid != String.Empty)
  1175. {
  1176. foreach (var selection in cachedSelection)
  1177. {
  1178. if (selection is SGBlackboardCategory blackboardCategory && blackboardCategory.viewModel.associatedCategoryGuid != associatedCategoryGuid)
  1179. {
  1180. associatedCategoryGuid = String.Empty;
  1181. // Also ensures it is added to the end of the default category
  1182. insertionIndex = -1;
  1183. }
  1184. }
  1185. }
  1186. // This ensures that if an item is duplicated, it is copied to the same category
  1187. if (copyGraph.copyPasteGraphSource == CopyPasteGraphSource.Duplicate)
  1188. {
  1189. associatedCategoryGuid = graphView.graph.FindCategoryForInput(input);
  1190. }
  1191. var copyShaderInputAction = new CopyShaderInputAction { shaderInputToCopy = input, containingCategoryGuid = associatedCategoryGuid };
  1192. copyShaderInputAction.insertIndex = insertionIndex;
  1193. if (graphView.graph.IsInputAllowedInGraph(input))
  1194. {
  1195. switch (input)
  1196. {
  1197. case AbstractShaderProperty property:
  1198. copyShaderInputAction.dependentNodeList = copyGraph.GetNodes<PropertyNode>().Where(x => x.property == input);
  1199. break;
  1200. case ShaderKeyword shaderKeyword:
  1201. copyShaderInputAction.dependentNodeList = copyGraph.GetNodes<KeywordNode>().Where(x => x.keyword == input);
  1202. // Pasting a new Keyword so need to test against variant limit
  1203. keywordsDirty = true;
  1204. break;
  1205. case ShaderDropdown shaderDropdown:
  1206. copyShaderInputAction.dependentNodeList = copyGraph.GetNodes<DropdownNode>().Where(x => x.dropdown == input);
  1207. dropdownsDirty = true;
  1208. break;
  1209. default:
  1210. AssertHelpers.Fail("Tried to paste shader input of unknown type into graph.");
  1211. break;
  1212. }
  1213. graphView.graph.owner.graphDataStore.Dispatch(copyShaderInputAction);
  1214. // Increment insertion index for next input
  1215. insertionIndex++;
  1216. }
  1217. }
  1218. // Make new categories from the copied graph
  1219. foreach (var category in copyGraph.categories)
  1220. {
  1221. foreach (var input in category.Children.ToList())
  1222. {
  1223. // Remove this input from being copied if its not allowed to be copied into the target graph (eg. its a dropdown and the target graph isn't a sub-graph)
  1224. if (graphView.graph.IsInputAllowedInGraph(input) == false)
  1225. category.RemoveItemFromCategory(input);
  1226. }
  1227. var copyCategoryAction = new CopyCategoryAction() { categoryToCopyReference = category };
  1228. graphView.graph.owner.graphDataStore.Dispatch(copyCategoryAction);
  1229. }
  1230. // Pasting a Sub Graph node that contains Keywords so need to test against variant limit
  1231. foreach (SubGraphNode subGraphNode in copyGraph.GetNodes<SubGraphNode>())
  1232. {
  1233. if (subGraphNode.asset.keywords.Any())
  1234. {
  1235. keywordsDirty = true;
  1236. }
  1237. if (subGraphNode.asset.dropdowns.Any())
  1238. {
  1239. dropdownsDirty = true;
  1240. }
  1241. }
  1242. // Test Keywords against variant limit
  1243. if (keywordsDirty)
  1244. {
  1245. graphView.graph.OnKeywordChangedNoValidate();
  1246. }
  1247. if (dropdownsDirty)
  1248. {
  1249. graphView.graph.OnDropdownChangedNoValidate();
  1250. }
  1251. using (ListPool<AbstractMaterialNode>.Get(out var remappedNodes))
  1252. {
  1253. using (ListPool<Graphing.Edge>.Get(out var remappedEdges))
  1254. {
  1255. var nodeList = copyGraph.GetNodes<AbstractMaterialNode>();
  1256. ClampNodesWithinView(graphView, new List<IRectInterface>().Union(nodeList).Union(copyGraph.stickyNotes));
  1257. graphView.graph.PasteGraph(copyGraph, remappedNodes, remappedEdges);
  1258. // Add new elements to selection
  1259. graphView.graphElements.ForEach(element =>
  1260. {
  1261. if (element is Edge edge && remappedEdges.Contains(edge.userData as IEdge))
  1262. graphView.AddToSelection(edge);
  1263. if (element is IShaderNodeView nodeView && remappedNodes.Contains(nodeView.node))
  1264. graphView.AddToSelection((Node)nodeView);
  1265. });
  1266. }
  1267. }
  1268. }
  1269. private static void ClampNodesWithinView(MaterialGraphView graphView, IEnumerable<IRectInterface> rectList)
  1270. {
  1271. // Compute the centroid of the copied elements at their original positions
  1272. var positions = rectList.Select(n => n.rect.position);
  1273. var centroid = UIUtilities.CalculateCentroid(positions);
  1274. /* Ensure nodes get pasted at cursor */
  1275. var graphMousePosition = graphView.contentViewContainer.WorldToLocal(graphView.cachedMousePosition);
  1276. var copiedNodesOrigin = graphMousePosition;
  1277. float xMin = float.MaxValue, xMax = float.MinValue, yMin = float.MaxValue, yMax = float.MinValue;
  1278. // Calculate bounding rectangle min and max coordinates for these elements, to use in clamping later
  1279. foreach (var element in rectList)
  1280. {
  1281. var position = element.rect.position;
  1282. xMin = Mathf.Min(xMin, position.x);
  1283. yMin = Mathf.Min(yMin, position.y);
  1284. xMax = Mathf.Max(xMax, position.x);
  1285. yMax = Mathf.Max(yMax, position.y);
  1286. }
  1287. // Get center of the current view
  1288. var center = graphView.contentViewContainer.WorldToLocal(graphView.layout.center);
  1289. // Get offset from center of view to mouse position
  1290. var mouseOffset = center - graphMousePosition;
  1291. var zoomAdjustedViewScale = 1.0f / graphView.scale;
  1292. var graphViewScaledHalfWidth = (graphView.layout.width * zoomAdjustedViewScale) / 2.0f;
  1293. var graphViewScaledHalfHeight = (graphView.layout.height * zoomAdjustedViewScale) / 2.0f;
  1294. const float widthThreshold = 40.0f;
  1295. const float heightThreshold = 20.0f;
  1296. if ((Mathf.Abs(mouseOffset.x) + widthThreshold > graphViewScaledHalfWidth ||
  1297. (Mathf.Abs(mouseOffset.y) + heightThreshold > graphViewScaledHalfHeight)))
  1298. {
  1299. // Out of bounds - Adjust taking into account the size of the bounding box around elements and the current graph zoom level
  1300. var adjustedPositionX = (xMax - xMin) + widthThreshold * zoomAdjustedViewScale;
  1301. var adjustedPositionY = (yMax - yMin) + heightThreshold * zoomAdjustedViewScale;
  1302. adjustedPositionY *= -1.0f * Mathf.Sign(copiedNodesOrigin.y);
  1303. adjustedPositionX *= -1.0f * Mathf.Sign(copiedNodesOrigin.x);
  1304. copiedNodesOrigin.x += adjustedPositionX;
  1305. copiedNodesOrigin.y += adjustedPositionY;
  1306. }
  1307. foreach (var element in rectList)
  1308. {
  1309. var rect = element.rect;
  1310. // Get the relative offset from the calculated centroid
  1311. var relativeOffsetFromCentroid = rect.position - centroid;
  1312. // Reapply that offset to ensure element positions are consistent when multiple elements are copied
  1313. rect.x = copiedNodesOrigin.x + relativeOffsetFromCentroid.x;
  1314. rect.y = copiedNodesOrigin.y + relativeOffsetFromCentroid.y;
  1315. element.rect = rect;
  1316. }
  1317. }
  1318. }
  1319. }