A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Linq;
5using UnityEngine;
6using UnityEditor.Graphing;
7using UnityEditor.Graphing.Util;
8using UnityEditor.ShaderGraph.Drawing.Inspector;
9using Object = UnityEngine.Object;
10
11using UnityEditor.Experimental.GraphView;
12using UnityEditor.ShaderGraph.Drawing.Colors;
13using UnityEngine.UIElements;
14using Edge = UnityEditor.Experimental.GraphView.Edge;
15using UnityEditor.VersionControl;
16using UnityEditor.Searcher;
17
18using Unity.Profiling;
19using UnityEditor.ShaderGraph.Internal;
20using UnityEditor.Experimental;
21using UnityEditor.PackageManager.UI;
22
23namespace UnityEditor.ShaderGraph.Drawing
24{
25 [Serializable]
26 class FloatingWindowsLayout
27 {
28 public WindowDockingLayout previewLayout = new WindowDockingLayout
29 {
30 dockingTop = false,
31 dockingLeft = false,
32 verticalOffset = 8,
33 horizontalOffset = 8
34 };
35 }
36
37 [Serializable]
38 class UserViewSettings
39 {
40 public bool isBlackboardVisible = true;
41 public bool isPreviewVisible = true;
42 public bool isInspectorVisible = true;
43 public string colorProvider = NoColors.Title;
44 }
45
46 class GraphEditorView : VisualElement, IDisposable
47 {
48 MaterialGraphView m_GraphView;
49 MasterPreviewView m_MasterPreviewView;
50 InspectorView m_InspectorView;
51
52 GraphData m_Graph;
53 PreviewManager m_PreviewManager;
54 MessageManager m_MessageManager;
55 SearchWindowProvider m_SearchWindowProvider;
56 EdgeConnectorListener m_EdgeConnectorListener;
57 VisualElement m_HoveredContextView;
58
59 BlackboardController m_BlackboardController;
60
61 internal BlackboardController blackboardController
62 {
63 get => m_BlackboardController;
64 set
65 {
66 if (value != null)
67 m_BlackboardController = value;
68 }
69 }
70
71 ColorManager m_ColorManager;
72 EditorWindow m_EditorWindow;
73
74 const string k_UserViewSettings = "UnityEditor.ShaderGraph.ToggleSettings";
75 UserViewSettings m_UserViewSettings;
76
77 internal UserViewSettings viewSettings { get => m_UserViewSettings; }
78
79 const string k_FloatingWindowsLayoutKey = "UnityEditor.ShaderGraph.FloatingWindowsLayout2";
80 FloatingWindowsLayout m_FloatingWindowsLayout = new FloatingWindowsLayout();
81
82 public Action saveRequested { get; set; }
83
84 public Action saveAsRequested { get; set; }
85
86 public Func<bool> isCheckedOut { get; set; }
87
88 public Action checkOut { get; set; }
89
90 public Action convertToSubgraphRequested
91 {
92 get { return m_GraphView.onConvertToSubgraphClick; }
93 set { m_GraphView.onConvertToSubgraphClick = value; }
94 }
95
96 public Action showInProjectRequested { get; set; }
97
98 public MaterialGraphView graphView
99 {
100 get { return m_GraphView; }
101 }
102
103 public InspectorView inspectorView
104 {
105 get { return m_InspectorView; }
106 }
107
108 internal PreviewManager previewManager
109 {
110 get { return m_PreviewManager; }
111 set { m_PreviewManager = value; }
112 }
113
114 public string assetName
115 {
116 get => m_AssetName;
117 set
118 {
119 m_AssetName = value;
120 // Also update blackboard title
121 m_BlackboardController.UpdateBlackboardTitle(m_AssetName);
122 }
123 }
124
125 public ColorManager colorManager
126 {
127 get => m_ColorManager;
128 }
129
130 void InstallSample(string sampleName)
131 {
132 var sample = Sample.FindByPackage("com.unity.shadergraph", null).SingleOrDefault(x => x.displayName == sampleName);
133 if (!string.IsNullOrEmpty(sample.displayName))
134 {
135 if (!sample.isImported)
136 {
137 sample.Import();
138 }
139 else
140 {
141 var reinstall = EditorUtility.DisplayDialog("Warning", "This sample package is already installed.\nDo you want to reinstall it?", "Yes", "No");
142 if (reinstall)
143 {
144 sample.Import(Sample.ImportOptions.OverridePreviousImports);
145 }
146 }
147 }
148 else
149 {
150 Debug.LogWarning($"Could not find sample package {sampleName}");
151 }
152 }
153
154 private static readonly ProfilerMarker AddGroupsMarker = new ProfilerMarker("AddGroups");
155 private static readonly ProfilerMarker AddStickyNotesMarker = new ProfilerMarker("AddStickyNotes");
156 public GraphEditorView(EditorWindow editorWindow, GraphData graph, MessageManager messageManager, string graphName)
157 {
158 m_GraphViewGroupTitleChanged = OnGroupTitleChanged;
159 m_GraphViewElementsAddedToGroup = OnElementsAddedToGroup;
160 m_GraphViewElementsRemovedFromGroup = OnElementsRemovedFromGroup;
161 ShaderGraphPreferences.onZoomStepSizeChanged += ResetZoom;
162
163 m_EditorWindow = editorWindow;
164 m_Graph = graph;
165 m_AssetName = graphName;
166 m_MessageManager = messageManager;
167 previewManager = new PreviewManager(graph, messageManager);
168 previewManager.RenderPreviews(m_EditorWindow, false);
169
170 styleSheets.Add(Resources.Load<StyleSheet>("Styles/GraphEditorView"));
171 var serializedSettings = EditorUserSettings.GetConfigValue(k_UserViewSettings);
172 m_UserViewSettings = JsonUtility.FromJson<UserViewSettings>(serializedSettings) ?? new UserViewSettings();
173 m_ColorManager = new ColorManager(m_UserViewSettings.colorProvider);
174
175
176 List<IShaderGraphToolbarExtension> toolbarExtensions = new();
177 foreach (var type in TypeCache.GetTypesDerivedFrom(typeof(IShaderGraphToolbarExtension)).Where(e => !e.IsGenericType))
178 {
179 toolbarExtensions.Add((IShaderGraphToolbarExtension)Activator.CreateInstance(type));
180 }
181
182 var colorProviders = m_ColorManager.providerNames.ToArray();
183 var toolbar = new IMGUIContainer(() =>
184 {
185 GUILayout.BeginHorizontal(EditorStyles.toolbar);
186 if (GUILayout.Button(new GUIContent(EditorGUIUtility.FindTexture("SaveActive"), "Save"), EditorStyles.toolbarButton))
187 {
188 if (saveRequested != null)
189 saveRequested();
190 }
191 if (GUILayout.Button(EditorResources.Load<Texture>("d_dropdown"), EditorStyles.toolbarButton))
192 {
193 GenericMenu menu = new GenericMenu();
194 menu.AddItem(new GUIContent("Save As..."), false, () => saveAsRequested());
195 menu.AddItem(new GUIContent("Show In Project"), false, () => showInProjectRequested());
196 if (!isCheckedOut() && Provider.enabled && Provider.isActive)
197 {
198 menu.AddItem(new GUIContent("Check Out"), false, () =>
199 {
200 if (checkOut != null)
201 checkOut();
202 });
203 }
204 else
205 {
206 menu.AddDisabledItem(new GUIContent("Check Out"), false);
207 }
208 menu.ShowAsContext();
209 }
210
211 if (graphView != null)
212 foreach (var ext in toolbarExtensions)
213 ext.OnGUI(graphView);
214
215 GUILayout.FlexibleSpace();
216
217 EditorGUI.BeginChangeCheck();
218 GUILayout.Label("Color Mode");
219 var newColorIndex = EditorGUILayout.Popup(m_ColorManager.activeIndex, colorProviders, GUILayout.Width(100f));
220 GUILayout.Space(4);
221 m_UserViewSettings.isBlackboardVisible = GUILayout.Toggle(m_UserViewSettings.isBlackboardVisible, new GUIContent(Resources.Load<Texture2D>("Icons/blackboard"), "Blackboard"), EditorStyles.toolbarButton);
222
223 GUILayout.Space(6);
224
225 m_UserViewSettings.isInspectorVisible = GUILayout.Toggle(m_UserViewSettings.isInspectorVisible, new GUIContent(EditorGUIUtility.TrIconContent("d_UnityEditor.InspectorWindow").image, "Graph Inspector"), EditorStyles.toolbarButton);
226
227 GUILayout.Space(6);
228
229 m_UserViewSettings.isPreviewVisible = GUILayout.Toggle(m_UserViewSettings.isPreviewVisible, new GUIContent(EditorGUIUtility.FindTexture("PreMatSphere"), "Main Preview"), EditorStyles.toolbarButton);
230
231 if (GUILayout.Button(new GUIContent(EditorGUIUtility.TrIconContent("_Help").image, "Open Shader Graph User Manual"), EditorStyles.toolbarButton))
232 {
233 Application.OpenURL(UnityEngine.Rendering.ShaderGraph.Documentation.GetPageLink("index"));
234 //Application.OpenURL("https://docs.unity3d.com/Packages/com.unity.shadergraph@17.0/manual/index.html"); // TODO : point to latest?
235 }
236 if (GUILayout.Button(EditorResources.Load<Texture>("d_dropdown"), EditorStyles.toolbarButton))
237 {
238 GenericMenu menu = new GenericMenu();
239 menu.AddItem(new GUIContent("Shader Graph Samples"), false, () =>
240 {
241 PackageManager.UI.Window.Open("com.unity.shadergraph");
242 });
243 menu.AddItem(new GUIContent("Install Node Reference Sample"), false, () =>
244 {
245 InstallSample("Node Reference");
246 });
247 menu.AddItem(new GUIContent("Install Procedural Patterns Sample"), false, () =>
248 {
249 InstallSample("Procedural Patterns");
250 });
251 menu.AddSeparator("");
252 menu.AddItem(new GUIContent("Shader Graph Feature Page"), false, () =>
253 {
254 Application.OpenURL("https://unity.com/features/shader-graph");
255 });
256 menu.AddItem(new GUIContent("Shader Graph Forums"), false, () =>
257 {
258 Application.OpenURL("https://forum.unity.com/forums/shader-graph.346/");
259 });
260 menu.AddItem(new GUIContent("Shader Graph Roadmap"), false, () =>
261 {
262 Application.OpenURL("https://portal.productboard.com/unity/1-unity-platform-rendering-visual-effects/tabs/7-shader-graph");
263 });
264 menu.ShowAsContext();
265 }
266
267 if (EditorGUI.EndChangeCheck())
268 {
269 UserViewSettingsChangeCheck(newColorIndex);
270 }
271 GUILayout.EndHorizontal();
272 });
273 Add(toolbar);
274
275 var content = new VisualElement { name = "content" };
276 {
277 m_GraphView = new MaterialGraphView(graph, () => m_PreviewManager.UpdateMasterPreview(ModificationScope.Topological))
278 { name = "GraphView", viewDataKey = "MaterialGraphView" };
279 ResetZoom();
280 m_GraphView.AddManipulator(new ContentDragger());
281 m_GraphView.AddManipulator(new SelectionDragger());
282 m_GraphView.AddManipulator(new RectangleSelector());
283 m_GraphView.AddManipulator(new ClickSelector());
284
285 // Bugfix 1312222. Running 'ResetSelectedBlockNodes' on all mouse up interactions will break selection
286 // after changing tabs. This was originally added to fix a bug with middle-mouse clicking while dragging a block node.
287 m_GraphView.RegisterCallback<MouseUpEvent>(evt => { if (evt.button == (int)MouseButton.MiddleMouse) m_GraphView.ResetSelectedBlockNodes(); });
288 // This takes care of when a property is dragged from BB and then the drag is ended by the Escape key, hides the scroll boundary regions and drag indicator if so
289 m_GraphView.RegisterCallback<DragExitedEvent>(evt =>
290 {
291 blackboardController.blackboard.OnDragExitedEvent(evt);
292 blackboardController.blackboard.hideDragIndicatorAction?.Invoke();
293 });
294
295 RegisterGraphViewCallbacks();
296 content.Add(m_GraphView);
297
298 string serializedWindowLayout = EditorUserSettings.GetConfigValue(k_FloatingWindowsLayoutKey);
299 if (!string.IsNullOrEmpty(serializedWindowLayout))
300 {
301 m_FloatingWindowsLayout = JsonUtility.FromJson<FloatingWindowsLayout>(serializedWindowLayout);
302 }
303
304 CreateMasterPreview();
305 CreateInspector();
306 CreateBlackboard();
307
308 UpdateSubWindowsVisibility();
309
310 m_GraphView.graphViewChanged = GraphViewChanged;
311
312 RegisterCallback<GeometryChangedEvent>(ApplySerializedWindowLayouts);
313 if (m_Graph.isSubGraph)
314 {
315 m_GraphView.AddToClassList("subgraph");
316 }
317 }
318
319 m_SearchWindowProvider = new SearcherProvider();
320 m_SearchWindowProvider.Initialize(editorWindow, m_Graph, m_GraphView);
321 m_GraphView.nodeCreationRequest = NodeCreationRequest;
322 //regenerate entries when graph view is refocused, to propogate subgraph changes
323 m_GraphView.RegisterCallback<FocusInEvent>(evt => { m_SearchWindowProvider.regenerateEntries = true; });
324
325 m_EdgeConnectorListener = new EdgeConnectorListener(m_Graph, m_SearchWindowProvider, editorWindow);
326
327 if (!m_Graph.isSubGraph)
328 {
329 AddContexts();
330 }
331
332 using (AddGroupsMarker.Auto())
333 {
334 foreach (var graphGroup in graph.groups)
335 AddGroup(graphGroup);
336 }
337
338 using (AddStickyNotesMarker.Auto())
339 {
340 foreach (var stickyNote in graph.stickyNotes)
341 AddStickyNote(stickyNote);
342 }
343
344 AddNodes(graph.GetNodes<AbstractMaterialNode>());
345 AddBlocks(graph.GetNodes<BlockNode>());
346 AddEdges(graph.edges);
347 Add(content);
348
349 // Active block lists need to be initialized on window start up
350 // Do this here to as we cant do this inside GraphData
351 // This is due to targets not being deserialized yet
352 var context = new TargetSetupContext();
353 foreach (var target in m_Graph.activeTargets)
354 {
355 target.Setup(ref context);
356 }
357 var activeBlocks = m_Graph.GetActiveBlocksForAllActiveTargets();
358 m_Graph.UpdateActiveBlocks(activeBlocks);
359
360 // Graph settings need to be initialized after the target setup
361 m_InspectorView.InitializeGraphSettings();
362 }
363
364 private void CreateBlackboard()
365 {
366 var blackboardViewModel = new BlackboardViewModel() { parentView = graphView, model = m_Graph, title = assetName };
367 m_BlackboardController = new BlackboardController(m_Graph, blackboardViewModel, m_Graph.owner.graphDataStore);
368 }
369
370 void AddContexts()
371 {
372 ContextView AddContext(string name, ContextData contextData, Direction portDirection)
373 {
374 //need to eventually remove this reference to editor window in context views
375 var contextView = new ContextView(name, contextData, m_EditorWindow);
376
377 // GraphView marks ContextViews' stacks, but not the actual root elements, as insertable. We want the
378 // contextual searcher menu to come up when *any* part of the ContextView is hovered. As a workaround,
379 // we keep track of the hovered ContextView and offer it if no targets are found.
380 contextView.RegisterCallback((MouseOverEvent _) => m_HoveredContextView = contextView);
381 contextView.RegisterCallback((MouseOutEvent _) =>
382 {
383 if (m_HoveredContextView == contextView) m_HoveredContextView = null;
384 });
385
386 contextView.SetPosition(new Rect(contextData.position, Vector2.zero));
387 contextView.AddPort(portDirection);
388 m_GraphView.AddElement(contextView);
389 return contextView;
390 }
391
392 // Add Contexts
393 // As Contexts are hardcoded and contain a single port we can just give the direction
394 var vertexContext = AddContext("Vertex", m_Graph.vertexContext, Direction.Output);
395 var fragmentContext = AddContext("Fragment", m_Graph.fragmentContext, Direction.Input);
396
397 // Connect Contexts
398 // Vertical Edges have no representation in Model
399 // Therefore just draw it and dont allow interaction
400 var contextEdge = new Edge()
401 {
402 output = vertexContext.port,
403 input = fragmentContext.port,
404 pickingMode = PickingMode.Ignore,
405 };
406 m_GraphView.AddElement(contextEdge);
407
408 // Update the Context list on MaterialGraphView
409 m_GraphView.UpdateContextList();
410 }
411
412 internal void UserViewSettingsChangeCheck(int newColorIndex)
413 {
414 if (newColorIndex != m_ColorManager.activeIndex)
415 {
416 m_ColorManager.SetActiveProvider(newColorIndex, m_GraphView.Query<MaterialNodeView>().ToList());
417 m_UserViewSettings.colorProvider = m_ColorManager.activeProviderName;
418 }
419
420 var serializedViewSettings = JsonUtility.ToJson(m_UserViewSettings);
421 EditorUserSettings.SetConfigValue(k_UserViewSettings, serializedViewSettings);
422
423 UpdateSubWindowsVisibility();
424 }
425
426 void NodeCreationRequest(NodeCreationContext c)
427 {
428 if (EditorWindow.focusedWindow == m_EditorWindow) //only display the search window when current graph view is focused
429 {
430 m_SearchWindowProvider.connectedPort = null;
431 m_SearchWindowProvider.target = c.target ?? m_HoveredContextView;
432 var displayPosition = graphView.cachedMousePosition;
433
434 SearcherWindow.Show(m_EditorWindow, (m_SearchWindowProvider as SearcherProvider).LoadSearchWindow(),
435 item => (m_SearchWindowProvider as SearcherProvider).OnSearcherSelectEntry(item, displayPosition),
436 displayPosition, null, new SearcherWindow.Alignment(SearcherWindow.Alignment.Vertical.Center, SearcherWindow.Alignment.Horizontal.Left));
437 }
438 }
439
440 // Master Preview, Inspector and Blackboard all need to keep their layouts when hidden in order to restore user preferences.
441 // Because of their differences we do this is different ways, for now.
442 void UpdateSubWindowsVisibility()
443 {
444 // Blackboard needs to be effectively removed when hidden to avoid bugs.
445 if (m_UserViewSettings.isBlackboardVisible)
446 blackboardController.blackboard.ShowWindow();
447 else
448 blackboardController.blackboard.HideWindow();
449
450 // Same for the inspector
451 if (m_UserViewSettings.isInspectorVisible)
452 m_InspectorView.ShowWindow();
453 else
454 m_InspectorView.HideWindow();
455
456 m_MasterPreviewView.visible = m_UserViewSettings.isPreviewVisible;
457 }
458
459 Action<Group, string> m_GraphViewGroupTitleChanged;
460 Action<Group, IEnumerable<GraphElement>> m_GraphViewElementsAddedToGroup;
461 Action<Group, IEnumerable<GraphElement>> m_GraphViewElementsRemovedFromGroup;
462
463 void RegisterGraphViewCallbacks()
464 {
465 m_GraphView.groupTitleChanged = m_GraphViewGroupTitleChanged;
466 m_GraphView.elementsAddedToGroup = m_GraphViewElementsAddedToGroup;
467 m_GraphView.elementsRemovedFromGroup = m_GraphViewElementsRemovedFromGroup;
468 }
469
470 void UnregisterGraphViewCallbacks()
471 {
472 m_GraphView.groupTitleChanged = null;
473 m_GraphView.elementsAddedToGroup = null;
474 m_GraphView.elementsRemovedFromGroup = null;
475 }
476
477 void CreateMasterPreview()
478 {
479 m_MasterPreviewView = new MasterPreviewView(previewManager, m_Graph) { name = "masterPreview" };
480
481 var masterPreviewViewDraggable = new WindowDraggable(null, this);
482 m_MasterPreviewView.AddManipulator(masterPreviewViewDraggable);
483 m_GraphView.Add(m_MasterPreviewView);
484
485 masterPreviewViewDraggable.OnDragFinished += UpdateSerializedWindowLayout;
486 m_MasterPreviewView.previewResizeBorderFrame.OnResizeFinished += UpdateSerializedWindowLayout;
487 }
488
489 void CreateInspector()
490 {
491 var inspectorViewModel = new InspectorViewModel() { parentView = this.graphView };
492 m_InspectorView = new InspectorView(inspectorViewModel);
493 graphView.OnSelectionChange += m_InspectorView.TriggerInspectorUpdate;
494 // Undo/redo actions that only affect selection don't trigger the above callback for some reason, so we also have to do this
495 Undo.undoRedoPerformed += (() => { m_InspectorView?.TriggerInspectorUpdate(graphView?.selection); });
496 }
497
498 // a nice curve that scales well for various HID (touchpad and mice).
499 static float WeightStepSize(float x) => Mathf.Clamp(2 * Mathf.Pow(x, 7f / 2f), 0.001f, 2.0f);
500 void ResetZoom()
501 {
502 var weightedStepSize = WeightStepSize(ShaderGraphPreferences.zoomStepSize);
503 m_GraphView?.SetupZoom(0.05f, 8.0f, weightedStepSize, 1.0f);
504 }
505
506 GraphViewChange GraphViewChanged(GraphViewChange graphViewChange)
507 {
508 if (graphViewChange.edgesToCreate != null)
509 {
510 foreach (var edge in graphViewChange.edgesToCreate)
511 {
512 var leftSlot = edge.output.GetSlot();
513 var rightSlot = edge.input.GetSlot();
514 if (leftSlot != null && rightSlot != null)
515 {
516 m_Graph.owner.RegisterCompleteObjectUndo("Connect Edge");
517 m_Graph.Connect(leftSlot.slotReference, rightSlot.slotReference);
518 }
519 }
520 graphViewChange.edgesToCreate.Clear();
521 }
522
523 if (graphViewChange.movedElements != null)
524 {
525 m_Graph.owner.RegisterCompleteObjectUndo("Move Elements");
526
527 List<GraphElement> nodesInsideGroup = new List<GraphElement>();
528 foreach (var element in graphViewChange.movedElements)
529 {
530 var groupNode = element as ShaderGroup;
531 if (groupNode == null)
532 continue;
533
534 foreach (GraphElement graphElement in groupNode.containedElements)
535 {
536 nodesInsideGroup.Add(graphElement);
537 }
538
539 SetGroupPosition(groupNode);
540 }
541
542 if (nodesInsideGroup.Any())
543 graphViewChange.movedElements.AddRange(nodesInsideGroup);
544
545 foreach (var element in graphViewChange.movedElements)
546 {
547 if (element.userData is AbstractMaterialNode node)
548 {
549 var drawState = node.drawState;
550 drawState.position = element.parent.ChangeCoordinatesTo(m_GraphView.contentViewContainer, element.GetPosition());
551 node.drawState = drawState;
552
553 // BlockNode moved outside a Context
554 // This isnt allowed but there is no way to disallow it on the GraphView
555 if (node is BlockNode blockNode &&
556 element.GetFirstAncestorOfType<ContextView>() == null)
557 {
558 var context = graphView.GetContext(blockNode.contextData);
559
560 // isDragging ensures we arent calling this when moving
561 // the BlockNode into the GraphView during dragging
562 if (context.isDragging)
563 continue;
564
565 // Remove from GraphView and add back to Context
566 m_GraphView.RemoveElement(element);
567 context.InsertBlock(element as MaterialNodeView);
568 }
569 }
570
571 if (element is StickyNote stickyNote)
572 {
573 SetStickyNotePosition(stickyNote);
574 }
575
576 if (element is ContextView contextView)
577 {
578 var rect = element.parent.ChangeCoordinatesTo(m_GraphView.contentViewContainer, element.GetPosition());
579 contextView.contextData.position = rect.position;
580 }
581 }
582 }
583
584 var nodesToUpdate = m_NodeViewHashSet;
585 nodesToUpdate.Clear();
586
587 if (graphViewChange.elementsToRemove != null)
588 {
589 m_Graph.owner.RegisterCompleteObjectUndo("Remove Elements");
590 m_Graph.RemoveElements(
591 graphViewChange.elementsToRemove.OfType<IShaderNodeView>().Select(v => v.node).ToArray(),
592 graphViewChange.elementsToRemove.OfType<Edge>().Select(e => (IEdge)e.userData).ToArray(),
593 graphViewChange.elementsToRemove.OfType<ShaderGroup>().Select(g => g.userData).ToArray(),
594 graphViewChange.elementsToRemove.OfType<StickyNote>().Select(n => n.userData).ToArray(),
595 graphViewChange.elementsToRemove.OfType<SGBlackboardField>().Select(f => (ShaderInput)f.userData).ToArray()
596 );
597 foreach (var edge in graphViewChange.elementsToRemove.OfType<Edge>())
598 {
599 if (edge.input != null)
600 {
601 if (edge.input.node is IShaderNodeView materialNodeView)
602 nodesToUpdate.Add(materialNodeView);
603 }
604 if (edge.output != null)
605 {
606 if (edge.output.node is IShaderNodeView materialNodeView)
607 nodesToUpdate.Add(materialNodeView);
608 }
609 }
610 }
611
612 foreach (var node in nodesToUpdate)
613 {
614 node.OnModified(ModificationScope.Topological);
615 }
616
617 UpdateEdgeColors(nodesToUpdate);
618 return graphViewChange;
619 }
620
621 void SetGroupPosition(ShaderGroup groupNode)
622 {
623 var pos = groupNode.GetPosition();
624 groupNode.userData.position = new Vector2(pos.x, pos.y);
625 }
626
627 void SetStickyNotePosition(StickyNote stickyNote)
628 {
629 var pos = stickyNote.GetPosition();
630 stickyNote.userData.position = new Rect(pos);
631 }
632
633 void OnGroupTitleChanged(Group graphGroup, string title)
634 {
635 var groupData = graphGroup.userData as GroupData;
636 if (groupData != null)
637 {
638 groupData.title = graphGroup.title;
639 }
640 }
641
642 void OnElementsAddedToGroup(Group graphGroup, IEnumerable<GraphElement> elements)
643 {
644 if (graphGroup.userData is GroupData groupData)
645 {
646 var anyChanged = false;
647 foreach (var element in elements)
648 {
649 if (element.userData is IGroupItem groupItem && groupItem.group != groupData)
650 {
651 anyChanged = true;
652 break;
653 }
654 }
655
656 if (!anyChanged)
657 return;
658
659 m_Graph.owner.RegisterCompleteObjectUndo(groupData.title);
660
661 foreach (var element in elements)
662 {
663 if (element.userData is IGroupItem groupItem)
664 {
665 m_Graph.SetGroup(groupItem, groupData);
666 }
667 }
668 }
669 }
670
671 void OnElementsRemovedFromGroup(Group graphGroup, IEnumerable<GraphElement> elements)
672 {
673 if (graphGroup.userData is GroupData groupData)
674 {
675 var anyChanged = false;
676 foreach (var element in elements)
677 {
678 if (element.userData is IGroupItem groupItem && groupItem.group == groupData)
679 {
680 anyChanged = true;
681 break;
682 }
683 }
684
685 if (!anyChanged)
686 return;
687
688 m_Graph.owner.RegisterCompleteObjectUndo("Ungroup Node(s)");
689
690 foreach (var element in elements)
691 {
692 if (element.userData is IGroupItem groupItem)
693 {
694 m_Graph.SetGroup(groupItem, null);
695 SetGroupPosition((ShaderGroup)graphGroup); //, (GraphElement)nodeView);
696 }
697 }
698 }
699 }
700
701
702
703
704 void OnNodeChanged(AbstractMaterialNode inNode, ModificationScope scope)
705 {
706 if (m_GraphView == null)
707 return;
708
709 var dependentNodes = new List<AbstractMaterialNode>();
710 if (!inNode.owner.graphIsConcretizing && !inNode.owner.replaceInProgress)
711 NodeUtils.CollectNodesNodeFeedsInto(dependentNodes, inNode);
712 else dependentNodes.Add(inNode);
713
714 foreach (var node in dependentNodes)
715 {
716 var nodeView = m_GraphView.GetNodeByGuid(node.objectId) as IShaderNodeView;
717 if (nodeView != null)
718 nodeView.OnModified(scope);
719 }
720 }
721
722
723 HashSet<IShaderNodeView> m_NodeViewHashSet = new HashSet<IShaderNodeView>();
724 HashSet<ShaderGroup> m_GroupHashSet = new HashSet<ShaderGroup>();
725 float lastUpdate = 0f;
726 public void HandleGraphChanges(bool wasUndoRedoPerformed)
727 {
728 UnregisterGraphViewCallbacks();
729 // anything that gets a new view needs to be updated. throughout this call.
730 // while it's a little expensive to build this every graph change, it's a huge
731 // improvement in overall performance to do so.
732 Dictionary<object, GraphElement> lookupTable = new();
733 m_GraphView.graphElements.ForEach(e => {
734 if (e.userData != null)
735 lookupTable.Add(e.userData, e);
736 });
737
738 previewManager.HandleGraphChanges();
739
740 var windowReceivesUpdates = (m_EditorWindow as MaterialGraphEditWindow)?.isVisible ?? false;
741 if (Time.realtimeSinceStartup - lastUpdate >= 0.03f && windowReceivesUpdates && m_UserViewSettings.isPreviewVisible)
742 {
743 lastUpdate = Time.realtimeSinceStartup;
744 previewManager.UpdateMasterPreview(ModificationScope.Node);
745 }
746 m_InspectorView.HandleGraphChanges();
747
748 if (m_Graph.addedEdges.Any() || m_Graph.removedEdges.Any())
749 {
750 // Precision color provider is the only one that needs to update node colors on connection.
751 if (m_ColorManager.activeProviderName == "Precision")
752 {
753 var nodeList = m_GraphView.Query<MaterialNodeView>().ToList();
754 m_ColorManager.SetNodesDirty(nodeList);
755 m_ColorManager.UpdateNodeViews(nodeList);
756 }
757 }
758
759 previewManager.RenderPreviews(m_EditorWindow);
760
761
762 m_GraphView.wasUndoRedoPerformed = wasUndoRedoPerformed;
763
764 if (wasUndoRedoPerformed || m_InspectorView.doesInspectorNeedUpdate)
765 m_InspectorView.Update();
766
767 if (wasUndoRedoPerformed)
768 m_GraphView.RestorePersistentSelectionAfterUndoRedo();
769
770 m_GroupHashSet.Clear();
771
772 HandleRemovedNodes(lookupTable);
773
774 foreach (var noteData in m_Graph.removedNotes)
775 {
776 if (lookupTable.TryGetValue(noteData, out var note))
777 m_GraphView.RemoveElement(note);
778 }
779
780 foreach (GroupData groupData in m_Graph.removedGroups)
781 {
782 if (lookupTable.TryGetValue(groupData, out var group))
783 m_GraphView.RemoveElement(group);
784 }
785
786 foreach (var groupData in m_Graph.addedGroups)
787 {
788 AddGroup(groupData, lookupTable: lookupTable);
789 }
790
791 foreach (var stickyNote in m_Graph.addedStickyNotes)
792 {
793 AddStickyNote(stickyNote, lookupTable: lookupTable);
794 }
795
796 foreach (var node in m_Graph.addedNodes)
797 {
798 AddNode(node, lookupTable: lookupTable);
799 }
800
801 foreach (var groupChange in m_Graph.parentGroupChanges)
802 {
803 GraphElement graphElement = null;
804 if (groupChange.groupItem is AbstractMaterialNode node)
805 {
806 lookupTable.TryGetValue(node, out graphElement);
807 }
808 else if (groupChange.groupItem is StickyNoteData stickyNote)
809 {
810 lookupTable.TryGetValue(stickyNote, out graphElement);
811 }
812 else
813 {
814 throw new InvalidOperationException("Unknown group item type.");
815 }
816
817 if (graphElement != null)
818 {
819 var groupView = graphElement.GetContainingScope() as ShaderGroup;
820 if (groupView?.userData != groupChange.newGroup)
821 {
822 groupView?.RemoveElement(graphElement);
823 if (groupChange.newGroup != null)
824 {
825 lookupTable.TryGetValue(groupChange.newGroup, out var newGroupView);
826 ((ShaderGroup)newGroupView).AddElement(graphElement);
827 }
828 }
829 }
830 }
831
832 foreach (var groupData in m_Graph.pastedGroups)
833 {
834 if (lookupTable.TryGetValue(groupData, out var group))
835 m_GraphView.AddToSelection(group);
836 }
837
838 foreach (var stickyNoteData in m_Graph.pastedStickyNotes)
839 {
840 if (lookupTable.TryGetValue(stickyNoteData, out var stickyNote))
841 m_GraphView.AddToSelection(stickyNote);
842 }
843
844 foreach (var node in m_Graph.pastedNodes)
845 {
846 if (lookupTable.TryGetValue(node.objectId, out var nodeView) && nodeView is IShaderNodeView)
847 m_GraphView.AddToSelection((Node)nodeView);
848 }
849
850 foreach (var shaderGroup in m_GroupHashSet)
851 {
852 SetGroupPosition(shaderGroup);
853 }
854
855 var nodesToUpdate = m_NodeViewHashSet;
856 nodesToUpdate.Clear();
857
858 foreach (var edge in m_Graph.removedEdges)
859 {
860 if (lookupTable.TryGetValue(edge, out var obj) && obj is Edge edgeView)
861 {
862 var nodeView = (IShaderNodeView)edgeView.input.node;
863 if (nodeView?.node != null)
864 {
865 nodesToUpdate.Add(nodeView);
866 }
867
868 edgeView.output.Disconnect(edgeView);
869 edgeView.input.Disconnect(edgeView);
870
871 edgeView.output = null;
872 edgeView.input = null;
873
874 m_GraphView.RemoveElement(edgeView);
875 }
876 }
877
878 foreach (var edge in m_Graph.addedEdges)
879 {
880 var edgeView = AddEdge(edge, lookupTable: lookupTable);
881 if (edgeView != null)
882 nodesToUpdate.Add((IShaderNodeView)edgeView.input.node);
883 }
884
885 foreach (var node in nodesToUpdate)
886 {
887 node.OnModified(ModificationScope.Topological);
888 }
889
890 UpdateEdgeColors(nodesToUpdate);
891
892 if (m_Graph.movedContexts)
893 {
894 foreach (var context in m_GraphView.contexts)
895 {
896 context.SetPosition(new Rect(context.contextData.position, Vector2.zero));
897 }
898 }
899
900 // Checking if any new Group Nodes just got added
901 if (m_Graph.mostRecentlyCreatedGroup != null)
902 {
903 var groups = m_GraphView.graphElements.ToList().OfType<ShaderGroup>();
904 foreach (ShaderGroup shaderGroup in groups)
905 {
906 if (shaderGroup.userData == m_Graph.mostRecentlyCreatedGroup)
907 {
908 shaderGroup.FocusTitleTextField();
909 break;
910 }
911 }
912 }
913
914 // If we auto-remove blocks and something has happened to trigger a check (don't re-check constantly)
915 if (m_Graph.checkAutoAddRemoveBlocks && ShaderGraphPreferences.autoAddRemoveBlocks)
916 {
917 var activeBlocks = m_Graph.GetActiveBlocksForAllActiveTargets();
918 m_Graph.AddRemoveBlocksFromActiveList(activeBlocks);
919 m_Graph.checkAutoAddRemoveBlocks = false;
920 // We have to re-check any nodes views that need to be removed since we already handled this above. After leaving this function the states on m_Graph will be cleared so we'll lose track of removed blocks.
921 HandleRemovedNodes(lookupTable);
922 }
923
924 UpdateBadges();
925
926 RegisterGraphViewCallbacks();
927 }
928
929 void HandleRemovedNodes(Dictionary<object, GraphElement> lookupTable = null)
930 {
931 foreach (var node in m_Graph.removedNodes)
932 {
933 node.UnregisterCallback(OnNodeChanged);
934 IShaderNodeView nodeView = null;
935 if (lookupTable != null && lookupTable.TryGetValue(node, out var nodeElement))
936 {
937 nodeView = nodeElement as IShaderNodeView;
938 }
939 else
940 {
941 nodeView = m_GraphView.nodes.ToList().OfType<IShaderNodeView>().FirstOrDefault(p => p.node != null && p.node == node);
942 }
943
944 // When deleting a node make sure to clear any input observers
945 switch (node)
946 {
947 case PropertyNode propertyNode:
948 propertyNode.property.RemoveObserver(propertyNode);
949 propertyNode.property.RemoveObserver(nodeView as IShaderInputObserver);
950 break;
951 case KeywordNode keywordNode:
952 keywordNode.keyword.RemoveObserver(keywordNode);
953 break;
954 case DropdownNode dropdownNode:
955 dropdownNode.dropdown.RemoveObserver(dropdownNode);
956 break;
957 }
958
959 if (nodeView != null)
960 {
961 nodeView.Dispose();
962
963 if (node is BlockNode blockNode)
964 {
965 var context = m_GraphView.GetContext(blockNode.contextData);
966 // blocknode may be floating and not actually in the stacknode's visual hierarchy.
967 if (context.Contains(nodeView as Node))
968 {
969 context.RemoveElement(nodeView as Node);
970 }
971 else
972 {
973 m_GraphView.RemoveElement((Node)nodeView);
974 }
975 }
976 else
977 {
978 m_GraphView.RemoveElement((Node)nodeView);
979 }
980
981 if (node.group != null)
982 {
983 if (lookupTable.TryGetValue(node.group, out var shaderGroup))
984 m_GroupHashSet.Add((ShaderGroup)shaderGroup);
985 }
986 }
987 }
988 }
989
990 void UpdateBadges()
991 {
992 if (!m_MessageManager.nodeMessagesChanged)
993 return;
994
995 foreach (var messageData in m_MessageManager.GetNodeMessages())
996 {
997 var node = m_Graph.GetNodeFromId(messageData.Key);
998
999 if (node == null || !(m_GraphView.GetNodeByGuid(node.objectId) is IShaderNodeView nodeView))
1000 continue;
1001
1002 if (messageData.Value.Count == 0)
1003 {
1004 nodeView.ClearMessage();
1005 }
1006 else
1007 {
1008 var foundMessage = messageData.Value.First();
1009 string messageString;
1010 if (foundMessage.line > 0)
1011 messageString = foundMessage.message + " at line " + foundMessage.line;
1012 else
1013 messageString = foundMessage.message;
1014 nodeView.AttachMessage(messageString, foundMessage.severity);
1015 }
1016 }
1017 }
1018
1019 List<GraphElement> m_GraphElementsTemp = new List<GraphElement>();
1020
1021 void AddNode(AbstractMaterialNode node, bool usePrebuiltVisualGroupMap = false, Dictionary<object, GraphElement> lookupTable = null)
1022 {
1023 var materialNode = node;
1024 Node nodeView;
1025 if (node is PropertyNode propertyNode)
1026 {
1027 var tokenNode = new PropertyNodeView(propertyNode, m_EdgeConnectorListener);
1028 m_GraphView.AddElement(tokenNode);
1029 nodeView = tokenNode;
1030
1031 // Register node model and node view as observer of property
1032 propertyNode.property.AddObserver(propertyNode);
1033 propertyNode.property.AddObserver(tokenNode);
1034 }
1035 else if (node is BlockNode blockNode)
1036 {
1037 var blockNodeView = new MaterialNodeView { userData = blockNode };
1038 blockNodeView.Initialize(blockNode, m_PreviewManager, m_EdgeConnectorListener, graphView);
1039 blockNodeView.MarkDirtyRepaint();
1040 nodeView = blockNodeView;
1041
1042 var context = m_GraphView.GetContext(blockNode.contextData);
1043 context.InsertBlock(blockNodeView);
1044 }
1045 else if (node is RedirectNodeData redirectNodeData)
1046 {
1047 var redirectNodeView = new RedirectNodeView { userData = redirectNodeData };
1048 m_GraphView.AddElement(redirectNodeView);
1049 redirectNodeView.ConnectToData(materialNode, m_EdgeConnectorListener);
1050 nodeView = redirectNodeView;
1051 }
1052 else
1053 {
1054 var materialNodeView = new MaterialNodeView { userData = materialNode };
1055
1056 // For keywords and dropdowns, we only register the node model itself as an observer,
1057 // the material node view redraws completely on changes so it doesn't need to be an observer
1058 switch (node)
1059 {
1060 case KeywordNode keywordNode:
1061 keywordNode.keyword.AddObserver(keywordNode);
1062 break;
1063 case DropdownNode dropdownNode:
1064 dropdownNode.dropdown.AddObserver(dropdownNode);
1065 break;
1066 }
1067
1068 m_GraphView.AddElement(materialNodeView);
1069 materialNodeView.Initialize(materialNode, m_PreviewManager, m_EdgeConnectorListener, graphView);
1070 m_ColorManager.UpdateNodeView(materialNodeView);
1071 nodeView = materialNodeView;
1072 }
1073
1074 node.RegisterCallback(OnNodeChanged);
1075 nodeView.MarkDirtyRepaint();
1076
1077 if (m_SearchWindowProvider.nodeNeedsRepositioning &&
1078 m_SearchWindowProvider.targetSlotReference.node == node)
1079 {
1080 m_SearchWindowProvider.nodeNeedsRepositioning = false;
1081 if (nodeView is IShaderNodeView shaderView &&
1082 shaderView.FindPort(m_SearchWindowProvider.targetSlotReference, out var port))
1083 {
1084 port.RegisterCallback<GeometryChangedEvent>(RepositionNode);
1085 return;
1086 }
1087 }
1088
1089 if (materialNode.group != null)
1090 {
1091 if (usePrebuiltVisualGroupMap)
1092 {
1093 // cheaper way to add the node to groups it is in
1094 ShaderGroup groupView;
1095 visualGroupMap.TryGetValue(materialNode.group, out groupView);
1096 if (groupView != null)
1097 groupView.AddElement(nodeView);
1098 }
1099 else
1100 {
1101 // This should also work for sticky notes
1102 m_GraphElementsTemp.Clear();
1103 m_GraphView.graphElements.ToList(m_GraphElementsTemp);
1104
1105 foreach (var element in m_GraphElementsTemp)
1106 {
1107 if (element is ShaderGroup groupView && groupView.userData == materialNode.group)
1108 {
1109 groupView.AddElement(nodeView);
1110 }
1111 }
1112 }
1113 }
1114
1115 lookupTable?.Add(node, nodeView);
1116 }
1117
1118 private static Dictionary<GroupData, ShaderGroup> visualGroupMap = new Dictionary<GroupData, ShaderGroup>();
1119 private static void AddToVisualGroupMap(GraphElement e)
1120 {
1121 if (e is ShaderGroup sg)
1122 {
1123 visualGroupMap.Add(sg.userData, sg);
1124 }
1125 }
1126
1127 private static Action<GraphElement> AddToVisualGroupMapAction = AddToVisualGroupMap;
1128 void BuildVisualGroupMap()
1129 {
1130 visualGroupMap.Clear();
1131 m_GraphView.graphElements.ForEach(AddToVisualGroupMapAction);
1132 }
1133
1134 private static readonly ProfilerMarker AddNodesMarker = new ProfilerMarker("AddNodes");
1135 void AddNodes(IEnumerable<AbstractMaterialNode> nodes)
1136 {
1137 using (AddNodesMarker.Auto())
1138 {
1139 BuildVisualGroupMap();
1140 foreach (var node in nodes)
1141 {
1142 // Skip BlockNodes as we need to order them
1143 if (node is BlockNode)
1144 continue;
1145
1146 AddNode(node, true);
1147 }
1148 visualGroupMap.Clear();
1149 }
1150 }
1151
1152 private static readonly ProfilerMarker AddBlocksMarker = new ProfilerMarker("AddBlocks");
1153 void AddBlocks(IEnumerable<BlockNode> blocks)
1154 {
1155 using (AddBlocksMarker.Auto())
1156 {
1157 // As they can be reordered, we cannot be sure BlockNodes are deserialized in the same order as their stack position
1158 // To handle this we reorder the BlockNodes here to avoid having to reorder them on the fly as they are added
1159 foreach (var node in blocks.OrderBy(s => s.index))
1160 {
1161 AddNode(node);
1162 }
1163 }
1164 }
1165
1166 void AddGroup(GroupData groupData, Dictionary<object, GraphElement> lookupTable = null)
1167 {
1168 ShaderGroup graphGroup = new ShaderGroup();
1169
1170 graphGroup.userData = groupData;
1171 graphGroup.title = groupData.title;
1172 graphGroup.SetPosition(new Rect(graphGroup.userData.position, Vector2.zero));
1173
1174 m_GraphView.AddElement(graphGroup);
1175 lookupTable?.Add(groupData, graphGroup);
1176 }
1177
1178 void AddStickyNote(StickyNoteData stickyNoteData, Dictionary<object, GraphElement> lookupTable = null)
1179 {
1180 var stickyNote = new StickyNote(stickyNoteData.position, m_Graph);
1181
1182 stickyNote.userData = stickyNoteData;
1183 stickyNote.viewDataKey = stickyNoteData.objectId;
1184 stickyNote.title = stickyNoteData.title;
1185 stickyNote.contents = stickyNoteData.content;
1186 stickyNote.textSize = (StickyNote.TextSize)stickyNoteData.textSize;
1187 stickyNote.theme = (StickyNote.Theme)stickyNoteData.theme;
1188 stickyNote.userData.group = stickyNoteData.group;
1189 stickyNote.SetPosition(new Rect(stickyNote.userData.position));
1190
1191 m_GraphView.AddElement(stickyNote);
1192 lookupTable?.Add(stickyNoteData, stickyNote);
1193
1194 // Add Sticky Note to group
1195 m_GraphElementsTemp.Clear();
1196 m_GraphView.graphElements.ToList(m_GraphElementsTemp);
1197
1198 if (stickyNoteData.group != null)
1199 {
1200 foreach (var element in m_GraphElementsTemp)
1201 {
1202 if (element is ShaderGroup groupView && groupView.userData == stickyNoteData.group)
1203 {
1204 groupView.AddElement(stickyNote);
1205 }
1206 }
1207 }
1208 }
1209
1210 static void RepositionNode(GeometryChangedEvent evt)
1211 {
1212 var port = evt.target as ShaderPort;
1213 if (port == null)
1214 return;
1215 port.UnregisterCallback<GeometryChangedEvent>(RepositionNode);
1216 var nodeView = port.node as IShaderNodeView;
1217 if (nodeView == null)
1218 return;
1219 var offset = nodeView.gvNode.mainContainer.WorldToLocal(port.GetGlobalCenter() + new Vector3(3f, 3f, 0f));
1220 var position = nodeView.gvNode.GetPosition();
1221 position.position -= offset;
1222 nodeView.gvNode.SetPosition(position);
1223 var drawState = nodeView.node.drawState;
1224 drawState.position = position;
1225 nodeView.node.drawState = drawState;
1226 nodeView.gvNode.MarkDirtyRepaint();
1227 port.MarkDirtyRepaint();
1228 }
1229
1230 private static Dictionary<AbstractMaterialNode, IShaderNodeView> visualNodeMap = new Dictionary<AbstractMaterialNode, IShaderNodeView>();
1231 private static void AddToVisualNodeMap(Node n)
1232 {
1233 IShaderNodeView snv = n as IShaderNodeView;
1234 if (snv != null)
1235 visualNodeMap.Add(snv.node, snv);
1236 }
1237
1238 private static Action<Node> AddToVisualNodeMapAction = AddToVisualNodeMap;
1239 void BuildVisualNodeMap()
1240 {
1241 visualNodeMap.Clear();
1242 m_GraphView.nodes.ForEach(AddToVisualNodeMapAction);
1243 }
1244
1245 private static readonly ProfilerMarker AddEdgesMarker = new ProfilerMarker("AddEdges");
1246 void AddEdges(IEnumerable<IEdge> edges)
1247 {
1248 using (AddEdgesMarker.Auto())
1249 {
1250 // fast way
1251 BuildVisualNodeMap();
1252 foreach (IEdge edge in edges)
1253 {
1254 AddEdge(edge, true, false);
1255 }
1256
1257 // apply the port update on every node
1258 foreach (IShaderNodeView nodeView in visualNodeMap.Values)
1259 {
1260 nodeView.gvNode.RefreshPorts();
1261 nodeView.UpdatePortInputTypes();
1262 }
1263
1264 // cleanup temp data
1265 visualNodeMap.Clear();
1266 }
1267 }
1268
1269 Edge AddEdge(IEdge edge, bool useVisualNodeMap = false, bool updateNodePorts = true, Dictionary<object, GraphElement> lookupTable = null)
1270 {
1271 var sourceNode = edge.outputSlot.node;
1272 if (sourceNode == null)
1273 {
1274 Debug.LogWarning("Source node is null");
1275 return null;
1276 }
1277 var sourceSlot = sourceNode.FindOutputSlot<MaterialSlot>(edge.outputSlot.slotId);
1278
1279 var targetNode = edge.inputSlot.node;
1280 if (targetNode == null)
1281 {
1282 Debug.LogWarning("Target node is null");
1283 return null;
1284 }
1285 var targetSlot = targetNode.FindInputSlot<MaterialSlot>(edge.inputSlot.slotId);
1286
1287 IShaderNodeView sourceNodeView = null;
1288 if (lookupTable != null)
1289 {
1290 lookupTable.TryGetValue(sourceNode, out var graphElement);
1291 sourceNodeView = (IShaderNodeView)graphElement;
1292 }
1293 else if (useVisualNodeMap)
1294 visualNodeMap.TryGetValue(sourceNode, out sourceNodeView);
1295
1296 if (sourceNodeView == null)
1297 sourceNodeView = m_GraphView.nodes.ToList().OfType<IShaderNodeView>().FirstOrDefault(x => x.node == sourceNode);
1298
1299 if (sourceNodeView != null)
1300 {
1301 sourceNodeView.FindPort(sourceSlot.slotReference, out var sourceAnchor);
1302
1303 IShaderNodeView targetNodeView = null;
1304 if (lookupTable != null)
1305 {
1306 lookupTable.TryGetValue(targetNode, out var graphElement);
1307 targetNodeView = (IShaderNodeView)graphElement;
1308 }
1309 else if (useVisualNodeMap)
1310 visualNodeMap.TryGetValue(targetNode, out targetNodeView);
1311
1312 if (targetNodeView == null)
1313 targetNodeView = m_GraphView.nodes.ToList().OfType<IShaderNodeView>().First(x => x.node == targetNode);
1314
1315 targetNodeView.FindPort(targetSlot.slotReference, out var targetAnchor);
1316
1317 var edgeView = new Edge
1318 {
1319 userData = edge,
1320 output = sourceAnchor,
1321 input = targetAnchor
1322 };
1323
1324 edgeView.RegisterCallback<MouseDownEvent>(OnMouseDown);
1325 edgeView.output.Connect(edgeView);
1326 edgeView.input.Connect(edgeView);
1327 m_GraphView.AddElement(edgeView);
1328
1329 if (updateNodePorts)
1330 {
1331 sourceNodeView.gvNode.RefreshPorts();
1332 targetNodeView.gvNode.RefreshPorts();
1333 sourceNodeView.UpdatePortInputTypes();
1334 targetNodeView.UpdatePortInputTypes();
1335 }
1336
1337 return edgeView;
1338 }
1339
1340 return null;
1341 }
1342
1343 void OnMouseDown(MouseDownEvent evt)
1344 {
1345 if (evt.button == (int)MouseButton.LeftMouse && evt.clickCount == 2)
1346 {
1347 if (evt.target is Edge edgeTarget)
1348 {
1349 Vector2 pos = evt.mousePosition;
1350 m_GraphView.CreateRedirectNode(pos, edgeTarget);
1351 }
1352 }
1353 }
1354
1355 Stack<Node> m_NodeStack = new Stack<Node>();
1356 string m_AssetName;
1357
1358 void UpdateEdgeColors(HashSet<IShaderNodeView> nodeViews)
1359 {
1360 var nodeStack = m_NodeStack;
1361 nodeStack.Clear();
1362 foreach (var nodeView in nodeViews)
1363 nodeStack.Push((Node)nodeView);
1364 PooledList<Edge> edgesToUpdate = PooledList<Edge>.Get();
1365 while (nodeStack.Any())
1366 {
1367 var nodeView = nodeStack.Pop();
1368 if (nodeView is IShaderNodeView shaderNodeView)
1369 {
1370 shaderNodeView.UpdatePortInputTypes();
1371 }
1372
1373 foreach (var anchorView in nodeView.outputContainer.Children().OfType<Port>())
1374 {
1375 foreach (var edgeView in anchorView.connections)
1376 {
1377 //update edges based on the active state of any modified nodes
1378 if (edgeView.input.node is MaterialNodeView inputNode && edgeView.output.node is MaterialNodeView outputNode)
1379 {
1380 //force redraw on update to prevent visual lag in the graph
1381 //Now has to be delayed a frame because setting port styles wont update colors till next frame
1382 edgesToUpdate.Add(edgeView);
1383 }
1384 //update edges based on dynamic vector length of any modified nodes
1385 var targetSlot = edgeView.input.GetSlot();
1386 if (targetSlot.valueType == SlotValueType.DynamicVector || targetSlot.valueType == SlotValueType.DynamicMatrix || targetSlot.valueType == SlotValueType.Dynamic)
1387 {
1388 var connectedNodeView = edgeView.input.node;
1389 if (connectedNodeView != null && !nodeViews.Contains((IShaderNodeView)connectedNodeView))
1390 {
1391 nodeStack.Push(connectedNodeView);
1392 nodeViews.Add((IShaderNodeView)connectedNodeView);
1393 }
1394 }
1395 }
1396 }
1397
1398 foreach (var anchorView in nodeView.inputContainer.Query<Port>().ToList())
1399 {
1400 var targetSlot = anchorView.GetSlot();
1401 if (targetSlot.valueType != SlotValueType.DynamicVector)
1402 continue;
1403 foreach (var edgeView in anchorView.connections)
1404 {
1405 //update edges based on the active state of any modified nodes
1406 if (edgeView.input.node is MaterialNodeView inputNode && edgeView.output.node is MaterialNodeView outputNode)
1407 {
1408 //force redraw on update to prevent visual lag in the graph
1409 //Now has to be delayed a frame because setting port styles wont update colors till next frame
1410 edgesToUpdate.Add(edgeView);
1411 }
1412 //update edge color for upstream dynamic vector types
1413 var connectedNodeView = edgeView.output.node;
1414 if (connectedNodeView != null && !nodeViews.Contains((IShaderNodeView)connectedNodeView))
1415 {
1416 nodeStack.Push(connectedNodeView);
1417 nodeViews.Add((IShaderNodeView)connectedNodeView);
1418 }
1419 }
1420 }
1421 }
1422 schedule.Execute(() =>
1423 {
1424 foreach (Edge e in edgesToUpdate)
1425 {
1426 e.UpdateEdgeControl();
1427 }
1428 edgesToUpdate.Dispose();
1429 }).StartingIn(0);
1430 }
1431
1432 void ApplySerializedWindowLayouts(GeometryChangedEvent evt)
1433 {
1434 UnregisterCallback<GeometryChangedEvent>(ApplySerializedWindowLayouts);
1435
1436 ApplyMasterPreviewLayout();
1437
1438 m_BlackboardController.blackboard.DeserializeLayout();
1439
1440 m_InspectorView.DeserializeLayout();
1441 }
1442
1443 void ApplyMasterPreviewLayout()
1444 {
1445 // If a preview size was loaded in from saved user settings use that
1446 if (m_FloatingWindowsLayout.previewLayout.size.x > 0f && m_FloatingWindowsLayout.previewLayout.size.y > 0f)
1447 {
1448 previewManager.ResizeMasterPreview(m_FloatingWindowsLayout.previewLayout.size);
1449 }
1450 else // Use default specified in the stylesheet for master preview
1451 {
1452 m_FloatingWindowsLayout.previewLayout.size = m_MasterPreviewView.layout.size;
1453 }
1454
1455 m_FloatingWindowsLayout.previewLayout.ApplyPosition(m_MasterPreviewView);
1456
1457 m_MasterPreviewView.style.width = m_FloatingWindowsLayout.previewLayout.size.x;
1458 m_MasterPreviewView.style.height = m_FloatingWindowsLayout.previewLayout.size.y;
1459 m_MasterPreviewView.RegisterCallback<GeometryChangedEvent>(SerializeMasterPreviewLayout);
1460 }
1461
1462 void SerializeMasterPreviewLayout(GeometryChangedEvent evt)
1463 {
1464 UpdateSerializedWindowLayout();
1465 }
1466
1467 void UpdateSerializedWindowLayout()
1468 {
1469 m_FloatingWindowsLayout.previewLayout.CalculateDockingCornerAndOffset(m_MasterPreviewView.layout, m_GraphView.layout);
1470 m_FloatingWindowsLayout.previewLayout.ClampToParentWindow();
1471
1472 blackboardController.blackboard.ClampToParentLayout(m_GraphView.layout);
1473
1474 m_InspectorView.ClampToParentLayout(m_GraphView.layout);
1475
1476 if (m_MasterPreviewView.visible)
1477 {
1478 m_FloatingWindowsLayout.previewLayout.size = m_MasterPreviewView.layout.size;
1479 }
1480
1481 string serializedWindowLayout = JsonUtility.ToJson(m_FloatingWindowsLayout);
1482 EditorUserSettings.SetConfigValue(k_FloatingWindowsLayoutKey, serializedWindowLayout);
1483 }
1484
1485 public void Dispose()
1486 {
1487 ShaderGraphPreferences.onZoomStepSizeChanged -= ResetZoom;
1488 if (m_GraphView != null)
1489 {
1490 saveRequested = null;
1491 saveAsRequested = null;
1492 convertToSubgraphRequested = null;
1493 showInProjectRequested = null;
1494 isCheckedOut = null;
1495 checkOut = null;
1496 foreach (var materialNodeView in m_GraphView.Query<MaterialNodeView>().ToList())
1497 materialNodeView.Dispose();
1498 foreach (var propertyNodeView in m_GraphView.Query<PropertyNodeView>().ToList())
1499 propertyNodeView.Dispose();
1500 foreach (var redirectNodeView in m_GraphView.Query<RedirectNodeView>().ToList())
1501 redirectNodeView.Dispose();
1502 foreach (var contextView in m_GraphView.Query<ContextView>().ToList())
1503 contextView.Dispose();
1504 foreach (var edge in m_GraphView.Query<Edge>().ToList())
1505 {
1506 edge.output = null;
1507 edge.input = null;
1508 }
1509
1510 m_GraphView.nodeCreationRequest = null;
1511 m_GraphView = null;
1512 }
1513
1514 m_BlackboardController?.Dispose();
1515 m_BlackboardController = null;
1516
1517 m_InspectorView?.Dispose();
1518 m_InspectorView = null;
1519
1520 if (previewManager != null)
1521 {
1522 previewManager.Dispose();
1523 previewManager = null;
1524 }
1525
1526 // Unload any static resources here
1527 Resources.UnloadAsset(ShaderPort.styleSheet);
1528
1529 if (m_SearchWindowProvider != null)
1530 {
1531 m_SearchWindowProvider.Dispose();
1532 m_SearchWindowProvider = null;
1533 }
1534 }
1535 }
1536}