using System; using System.Collections.Generic; using System.Linq; using UnityEditor.Experimental.UIElements.GraphView; using UnityEditor.ShaderGraph.Drawing; using UnityEngine; using UnityEngine.Experimental.UIElements; using UnityEditor.ShaderGraph; using UnityEditor.Graphing; using UnityEditor.ShaderGraph.Drawing.Inspector; using Edge = UnityEditor.Experimental.UIElements.GraphView.Edge; using Object = UnityEngine.Object; namespace UnityEditor.ShaderGraph.Drawing { public class GraphEditorView : VisualElement, IDisposable { AbstractMaterialGraph m_Graph; MaterialGraphView m_GraphView; GraphInspectorView m_GraphInspectorView; ToolbarView m_ToolbarView; ToolbarButtonView m_TimeButton; ToolbarButtonView m_CopyToClipboardButton; PreviewManager m_PreviewManager; public Action onUpdateAssetClick { get; set; } public Action onConvertToSubgraphClick { get; set; } public Action onShowInProjectClick { get; set; } public MaterialGraphView graphView { get { return m_GraphView; } } public PreviewRate previewRate { get { return previewManager.previewRate; } set { previewManager.previewRate = value; } } public PreviewManager previewManager { get { return m_PreviewManager; } set { m_PreviewManager = value; } } public GraphInspectorView inspectorView { get { return m_GraphInspectorView; } } public GraphEditorView(AbstractMaterialGraph graph, string assetName) { m_Graph = graph; AddStyleSheetPath("Styles/MaterialGraph"); previewManager = new PreviewManager(graph); m_ToolbarView = new ToolbarView { name = "TitleBar" }; { m_ToolbarView.Add(new ToolbarSpaceView()); m_ToolbarView.Add(new ToolbarSeparatorView()); var updateAssetButton = new ToolbarButtonView { text = "Update asset" }; updateAssetButton.AddManipulator(new Clickable(() => { if (onUpdateAssetClick != null) onUpdateAssetClick(); })); m_ToolbarView.Add(updateAssetButton); m_ToolbarView.Add(new ToolbarSeparatorView()); m_ToolbarView.Add(new ToolbarSpaceView()); m_ToolbarView.Add(new ToolbarSeparatorView()); var convertToSubgraphButton = new ToolbarButtonView { text = "Convert to subgraph" }; convertToSubgraphButton.AddManipulator(new Clickable(() => { if (onConvertToSubgraphClick != null) onConvertToSubgraphClick(); })); m_ToolbarView.Add(convertToSubgraphButton); m_ToolbarView.Add(new ToolbarSeparatorView()); m_ToolbarView.Add(new ToolbarSpaceView()); m_ToolbarView.Add(new ToolbarSeparatorView()); var showInProjectButton = new ToolbarButtonView { text = "Show in project" }; showInProjectButton.AddManipulator(new Clickable(() => { if (onShowInProjectClick != null) onShowInProjectClick(); })); m_ToolbarView.Add(showInProjectButton); m_ToolbarView.Add(new ToolbarSeparatorView()); m_ToolbarView.Add(new ToolbarSpaceView()); m_ToolbarView.Add(new ToolbarSeparatorView()); m_TimeButton = new ToolbarButtonView { text = "Preview rate: " + previewRate }; m_TimeButton.AddManipulator(new Clickable(() => { if (previewRate == PreviewRate.Full) previewRate = PreviewRate.Throttled; else if (previewRate == PreviewRate.Throttled) previewRate = PreviewRate.Off; else if (previewRate == PreviewRate.Off) previewRate = PreviewRate.Full; m_TimeButton.text = "Preview rate: " + previewRate; })); m_ToolbarView.Add(m_TimeButton); m_ToolbarView.Add(new ToolbarSeparatorView()); m_CopyToClipboardButton = new ToolbarButtonView() { text = "Copy shader to clipboard" }; m_CopyToClipboardButton.AddManipulator(new Clickable(() => { AbstractMaterialNode copyFromNode = graph.GetNodes().First(); if (graphView.selection.Count == 1) { MaterialNodeView selectedNodeView = graphView.selection[0] as MaterialNodeView; if (selectedNodeView.node != null && selectedNodeView.node.hasPreview) { copyFromNode = selectedNodeView.node; } } var textureInfo = new List(); PreviewMode previewMode; FloatShaderProperty outputIdProperty; string shader = graph.GetShader(copyFromNode, GenerationMode.ForReals, assetName, out textureInfo, out previewMode, out outputIdProperty); GUIUtility.systemCopyBuffer = shader; } )); m_ToolbarView.Add(m_CopyToClipboardButton); m_ToolbarView.Add(new ToolbarSeparatorView()); } Add(m_ToolbarView); var content = new VisualElement { name = "content" }; { m_GraphView = new MaterialGraphView(graph) { name = "GraphView", persistenceKey = "MaterialGraphView" }; m_GraphView.SetupZoom(ContentZoomer.DefaultMinScale, ContentZoomer.DefaultMaxScale); m_GraphView.AddManipulator(new ContentDragger()); m_GraphView.AddManipulator(new RectangleSelector()); m_GraphView.AddManipulator(new SelectionDragger()); m_GraphView.AddManipulator(new ClickSelector()); m_GraphView.AddManipulator(new NodeCreator(graph)); m_GraphView.AddManipulator(new GraphDropTarget(graph)); content.Add(m_GraphView); m_GraphInspectorView = new GraphInspectorView(assetName, previewManager, graph) { name = "inspector" }; m_GraphView.onSelectionChanged += m_GraphInspectorView.UpdateSelection; content.Add(m_GraphInspectorView); m_GraphView.graphViewChanged = GraphViewChanged; } foreach (var node in graph.GetNodes()) AddNode(node); foreach (var edge in graph.edges) AddEdge(edge); Add(content); } GraphViewChange GraphViewChanged(GraphViewChange graphViewChange) { if (graphViewChange.edgesToCreate != null) { foreach (var edge in graphViewChange.edgesToCreate) { var leftSlot = edge.output.userData as ISlot; var rightSlot = edge.input.userData as ISlot; if (leftSlot != null && rightSlot != null) { m_Graph.owner.RegisterCompleteObjectUndo("Connect Edge"); m_Graph.Connect(leftSlot.slotReference, rightSlot.slotReference); } } graphViewChange.edgesToCreate.Clear(); } if (graphViewChange.movedElements != null) { foreach (var element in graphViewChange.movedElements) { var node = element.userData as INode; if (node == null) continue; var drawState = node.drawState; drawState.position = element.layout; node.drawState = drawState; } } return graphViewChange; } void OnNodeChanged(INode inNode, ModificationScope scope) { if (m_GraphView == null) return; var dependentNodes = new List(); NodeUtils.CollectNodesNodeFeedsInto(dependentNodes, inNode); foreach (var node in dependentNodes) { var theViews = m_GraphView.nodes.ToList().OfType(); var viewsFound = theViews.Where(x => x.node.guid == node.guid).ToList(); foreach (var drawableNodeData in viewsFound) drawableNodeData.OnModified(scope); } } HashSet m_NodeViewHashSet = new HashSet(); public void HandleGraphChanges() { previewManager.HandleGraphChanges(); previewManager.RenderPreviews(); inspectorView.HandleGraphChanges(); foreach (var node in m_Graph.removedNodes) { node.onModified -= OnNodeChanged; var nodeView = m_GraphView.nodes.ToList().OfType().FirstOrDefault(p => p.node != null && p.node.guid == node.guid); if (nodeView != null) { nodeView.Dispose(); nodeView.userData = null; m_GraphView.RemoveElement(nodeView); } } foreach (var node in m_Graph.addedNodes) AddNode(node); var nodesToUpdate = m_NodeViewHashSet; nodesToUpdate.Clear(); foreach (var edge in m_Graph.removedEdges) { var edgeView = m_GraphView.graphElements.ToList().OfType().FirstOrDefault(p => p.userData is IEdge && Equals((IEdge)p.userData, edge)); if (edgeView != null) { var nodeView = edgeView.input.node as MaterialNodeView; if (nodeView != null && nodeView.node != null) { nodesToUpdate.Add(nodeView); } edgeView.output.Disconnect(edgeView); edgeView.input.Disconnect(edgeView); m_GraphView.RemoveElement(edgeView); } } foreach (var edge in m_Graph.addedEdges) { var edgeView = AddEdge(edge); if (edgeView != null) nodesToUpdate.Add((MaterialNodeView)edgeView.input.node); } foreach (var node in nodesToUpdate) node.UpdatePortInputVisibilities(); UpdateEdgeColors(nodesToUpdate); } void AddNode(INode node) { var nodeView = new MaterialNodeView { userData = node }; m_GraphView.AddElement(nodeView); nodeView.Initialize(m_GraphView, node as AbstractMaterialNode, m_PreviewManager); node.onModified += OnNodeChanged; } Edge AddEdge(IEdge edge) { var sourceNode = m_Graph.GetNodeFromGuid(edge.outputSlot.nodeGuid); if (sourceNode == null) { Debug.LogWarning("Source node is null"); return null; } var sourceSlot = sourceNode.FindOutputSlot(edge.outputSlot.slotId); var targetNode = m_Graph.GetNodeFromGuid(edge.inputSlot.nodeGuid); if (targetNode == null) { Debug.LogWarning("Target node is null"); return null; } var targetSlot = targetNode.FindInputSlot(edge.inputSlot.slotId); var sourceNodeView = m_GraphView.nodes.ToList().OfType().FirstOrDefault(x => x.node == sourceNode); if (sourceNodeView != null) { var sourceAnchor = sourceNodeView.outputContainer.Children().OfType().FirstOrDefault(x => x.userData is ISlot && (x.userData as ISlot).Equals(sourceSlot)); var targetNodeView = m_GraphView.nodes.ToList().OfType().FirstOrDefault(x => x.node == targetNode); var targetAnchor = targetNodeView.inputContainer.Children().OfType().FirstOrDefault(x => x.userData is ISlot && (x.userData as ISlot).Equals(targetSlot)); var edgeView = new Edge { userData = edge, output = sourceAnchor, input = targetAnchor }; // edgeView.UpdateClasses(sourceSlot.concreteValueType, targetSlot.concreteValueType); edgeView.output.Connect(edgeView); edgeView.input.Connect(edgeView); m_GraphView.AddElement(edgeView); sourceNodeView.RefreshPorts(); targetNodeView.RefreshPorts(); sourceNodeView.UpdatePortInputTypes(); targetNodeView.UpdatePortInputTypes(); return edgeView; } return null; } Stack m_NodeStack = new Stack(); void UpdateEdgeColors(HashSet nodeViews) { var nodeStack = m_NodeStack; nodeStack.Clear(); foreach (var nodeView in nodeViews) nodeStack.Push(nodeView); while (nodeStack.Any()) { var nodeView = nodeStack.Pop(); nodeView.UpdatePortInputTypes(); foreach (var anchorView in nodeView.outputContainer.Children().OfType()) { var sourceSlot = (MaterialSlot)anchorView.userData; foreach (var edgeView in anchorView.connections.OfType()) { var targetSlot = (MaterialSlot)edgeView.input.userData; if (targetSlot.valueType == SlotValueType.Dynamic) { // edgeView.UpdateClasses(sourceSlot.concreteValueType, targetSlot.concreteValueType); var connectedNodeView = edgeView.input.node as MaterialNodeView; if (connectedNodeView != null && !nodeViews.Contains(connectedNodeView)) { nodeStack.Push(connectedNodeView); nodeViews.Add(connectedNodeView); } } } } foreach (var anchorView in nodeView.inputContainer.Children().OfType()) { var targetSlot = (MaterialSlot)anchorView.userData; if (targetSlot.valueType != SlotValueType.Dynamic) continue; foreach (var edgeView in anchorView.connections.OfType()) { var sourceSlot = (MaterialSlot)edgeView.output.userData; // edgeView.UpdateClasses(sourceSlot.concreteValueType, targetSlot.concreteValueType); var connectedNodeView = edgeView.output.node as MaterialNodeView; if (connectedNodeView != null && !nodeViews.Contains(connectedNodeView)) { nodeStack.Push(connectedNodeView); nodeViews.Add(connectedNodeView); } } } } } public void Dispose() { onUpdateAssetClick = null; onConvertToSubgraphClick = null; onShowInProjectClick = null; if (m_GraphView != null) { foreach (var node in m_GraphView.Children().OfType()) node.Dispose(); m_GraphView = null; } if (m_GraphInspectorView != null) m_GraphInspectorView.Dispose(); if (previewManager != null) { previewManager.Dispose(); previewManager = null; } } } }