您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
551 行
22 KiB
551 行
22 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using UnityEditor.Experimental.UIElements.GraphView;
|
|
using UnityEngine;
|
|
using UnityEngine.Experimental.UIElements;
|
|
using UnityEditor.Graphing;
|
|
using UnityEditor.ShaderGraph.Drawing.Controls;
|
|
using UnityEngine.Experimental.Rendering;
|
|
using UnityEngine.Experimental.UIElements.StyleEnums;
|
|
using UnityEngine.Experimental.UIElements.StyleSheets;
|
|
using UnityEngine.Rendering;
|
|
using Node = UnityEditor.Experimental.UIElements.GraphView.Node;
|
|
|
|
namespace UnityEditor.ShaderGraph.Drawing
|
|
{
|
|
public sealed class MaterialNodeView : Node
|
|
{
|
|
PreviewRenderData m_PreviewRenderData;
|
|
Image m_PreviewImage;
|
|
VisualElement m_PreviewContainer;
|
|
VisualElement m_ControlItems;
|
|
VisualElement m_PreviewFiller;
|
|
VisualElement m_ControlsDivider;
|
|
IEdgeConnectorListener m_ConnectorListener;
|
|
VisualElement m_PortInputContainer;
|
|
VisualElement m_SettingsContainer;
|
|
bool m_ShowSettings = false;
|
|
VisualElement m_SettingsButton;
|
|
VisualElement m_Settings;
|
|
VisualElement m_NodeSettingsView;
|
|
|
|
|
|
public void Initialize(AbstractMaterialNode inNode, PreviewManager previewManager, IEdgeConnectorListener connectorListener)
|
|
{
|
|
AddStyleSheetPath("Styles/MaterialNodeView");
|
|
AddToClassList("MaterialNode");
|
|
|
|
if (inNode == null)
|
|
return;
|
|
|
|
var contents = this.Q("contents");
|
|
|
|
m_ConnectorListener = connectorListener;
|
|
node = inNode;
|
|
persistenceKey = node.guid.ToString();
|
|
UpdateTitle();
|
|
|
|
// Add controls container
|
|
var controlsContainer = new VisualElement { name = "controls" };
|
|
{
|
|
m_ControlsDivider = new VisualElement { name = "divider" };
|
|
m_ControlsDivider.AddToClassList("horizontal");
|
|
controlsContainer.Add(m_ControlsDivider);
|
|
m_ControlItems = new VisualElement { name = "items" };
|
|
controlsContainer.Add(m_ControlItems);
|
|
|
|
// Instantiate control views from node
|
|
foreach (var propertyInfo in node.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
|
foreach (IControlAttribute attribute in propertyInfo.GetCustomAttributes(typeof(IControlAttribute), false))
|
|
m_ControlItems.Add(attribute.InstantiateControl(node, propertyInfo));
|
|
}
|
|
if (m_ControlItems.childCount > 0)
|
|
contents.Add(controlsContainer);
|
|
|
|
if (node.hasPreview)
|
|
{
|
|
// Add actual preview which floats on top of the node
|
|
m_PreviewContainer = new VisualElement
|
|
{
|
|
name = "previewContainer",
|
|
clippingOptions = ClippingOptions.ClipAndCacheContents,
|
|
pickingMode = PickingMode.Ignore
|
|
};
|
|
m_PreviewImage = new Image
|
|
{
|
|
name = "preview",
|
|
pickingMode = PickingMode.Ignore,
|
|
image = Texture2D.whiteTexture,
|
|
};
|
|
{
|
|
// Add preview collapse button on top of preview
|
|
var collapsePreviewButton = new VisualElement { name = "collapse" };
|
|
collapsePreviewButton.Add(new VisualElement { name = "icon" });
|
|
collapsePreviewButton.AddManipulator(new Clickable(() =>
|
|
{
|
|
node.owner.owner.RegisterCompleteObjectUndo("Collapse Preview");
|
|
UpdatePreviewExpandedState(false);
|
|
}));
|
|
m_PreviewImage.Add(collapsePreviewButton);
|
|
}
|
|
m_PreviewContainer.Add(m_PreviewImage);
|
|
|
|
// Hook up preview image to preview manager
|
|
m_PreviewRenderData = previewManager.GetPreview(inNode);
|
|
m_PreviewRenderData.onPreviewChanged += UpdatePreviewTexture;
|
|
UpdatePreviewTexture();
|
|
|
|
// Add fake preview which pads out the node to provide space for the floating preview
|
|
m_PreviewFiller = new VisualElement { name = "previewFiller" };
|
|
m_PreviewFiller.AddToClassList("expanded");
|
|
{
|
|
var previewDivider = new VisualElement { name = "divider" };
|
|
previewDivider.AddToClassList("horizontal");
|
|
m_PreviewFiller.Add(previewDivider);
|
|
|
|
var expandPreviewButton = new VisualElement { name = "expand" };
|
|
expandPreviewButton.Add(new VisualElement { name = "icon" });
|
|
expandPreviewButton.AddManipulator(new Clickable(() =>
|
|
{
|
|
node.owner.owner.RegisterCompleteObjectUndo("Expand Preview");
|
|
UpdatePreviewExpandedState(true);
|
|
}));
|
|
m_PreviewFiller.Add(expandPreviewButton);
|
|
}
|
|
contents.Add(m_PreviewFiller);
|
|
|
|
UpdatePreviewExpandedState(node.previewExpanded);
|
|
}
|
|
|
|
// Add port input container, which acts as a pixel cache for all port inputs
|
|
m_PortInputContainer = new VisualElement
|
|
{
|
|
name = "portInputContainer",
|
|
clippingOptions = ClippingOptions.ClipAndCacheContents,
|
|
pickingMode = PickingMode.Ignore
|
|
};
|
|
Add(m_PortInputContainer);
|
|
|
|
AddSlots(node.GetSlots<MaterialSlot>());
|
|
UpdatePortInputs();
|
|
base.expanded = node.drawState.expanded;
|
|
RefreshExpandedState(); //This should not be needed. GraphView needs to improve the extension api here
|
|
UpdatePortInputVisibilities();
|
|
|
|
SetPosition(new Rect(node.drawState.position.x, node.drawState.position.y, 0, 0));
|
|
|
|
if (node is SubGraphNode)
|
|
{
|
|
RegisterCallback<MouseDownEvent>(OnSubGraphDoubleClick);
|
|
}
|
|
|
|
var masterNode = node as IMasterNode;
|
|
if (masterNode != null)
|
|
{
|
|
if (!masterNode.IsPipelineCompatible(GraphicsSettings.renderPipelineAsset))
|
|
{
|
|
IconBadge wrongPipeline = IconBadge.CreateError("The current render pipeline is not compatible with this master node.");
|
|
Add(wrongPipeline);
|
|
VisualElement title = this.Q("title");
|
|
wrongPipeline.AttachTo(title, SpriteAlignment.LeftCenter);
|
|
}
|
|
}
|
|
|
|
m_PortInputContainer.SendToBack();
|
|
|
|
// Remove this after updated to the correct API call has landed in trunk. ------------
|
|
VisualElement m_TitleContainer;
|
|
VisualElement m_ButtonContainer;
|
|
m_TitleContainer = this.Q("title");
|
|
// -----------------------------------------------------------------------------------
|
|
|
|
var settings = node as IHasSettings;
|
|
if (settings != null)
|
|
{
|
|
m_NodeSettingsView = new NodeSettingsView();
|
|
m_NodeSettingsView.visible = false;
|
|
|
|
Add(m_NodeSettingsView);
|
|
|
|
m_SettingsButton = new VisualElement {name = "settings-button"};
|
|
m_SettingsButton.Add(new VisualElement { name = "icon" });
|
|
|
|
m_Settings = settings.CreateSettingsElement();
|
|
|
|
m_SettingsButton.AddManipulator(new Clickable(() =>
|
|
{
|
|
UpdateSettingsExpandedState();
|
|
}));
|
|
|
|
// Remove this after updated to the correct API call has landed in trunk. ------------
|
|
m_ButtonContainer = new VisualElement { name = "button-container" };
|
|
m_ButtonContainer.style.flexDirection = StyleValue<FlexDirection>.Create(FlexDirection.Row);
|
|
m_ButtonContainer.Add(m_SettingsButton);
|
|
m_ButtonContainer.Add(m_CollapseButton);
|
|
m_TitleContainer.Add(m_ButtonContainer);
|
|
// -----------------------------------------------------------------------------------
|
|
//titleButtonContainer.Add(m_SettingsButton);
|
|
//titleButtonContainer.Add(m_CollapseButton);
|
|
|
|
RegisterCallback<GeometryChangedEvent>(OnGeometryChanged);
|
|
}
|
|
}
|
|
|
|
void OnGeometryChanged(GeometryChangedEvent evt)
|
|
{
|
|
// style.positionTop and style.positionLeft are in relation to the parent,
|
|
// so we translate the layout of the settings button to be in the coordinate
|
|
// space of the settings view's parent.
|
|
|
|
var settingsButtonLayout = m_SettingsButton.ChangeCoordinatesTo(m_NodeSettingsView.parent, m_SettingsButton.layout);
|
|
m_NodeSettingsView.style.positionTop = settingsButtonLayout.yMax - 18f;
|
|
m_NodeSettingsView.style.positionLeft = settingsButtonLayout.xMin - 16f;
|
|
}
|
|
|
|
void OnSubGraphDoubleClick(MouseDownEvent evt)
|
|
{
|
|
if (evt.clickCount == 2 && evt.button == 0)
|
|
{
|
|
SubGraphNode subgraphNode = node as SubGraphNode;
|
|
|
|
var path = AssetDatabase.GetAssetPath(subgraphNode.subGraphAsset);
|
|
ShaderGraphImporterEditor.ShowGraphEditWindow(path);
|
|
}
|
|
}
|
|
|
|
public AbstractMaterialNode node { get; private set; }
|
|
|
|
public override bool expanded
|
|
{
|
|
get { return base.expanded; }
|
|
set
|
|
{
|
|
if (base.expanded != value)
|
|
base.expanded = value;
|
|
|
|
if (node.drawState.expanded != value)
|
|
{
|
|
var ds = node.drawState;
|
|
ds.expanded = value;
|
|
node.drawState = ds;
|
|
}
|
|
|
|
RefreshExpandedState(); //This should not be needed. GraphView needs to improve the extension api here
|
|
UpdatePortInputVisibilities();
|
|
}
|
|
}
|
|
|
|
public override void BuildContextualMenu(ContextualMenuPopulateEvent evt)
|
|
{
|
|
if (evt.target is Node)
|
|
{
|
|
evt.menu.AppendAction("Copy Shader", CopyToClipboard, node.hasPreview ? ContextualMenu.MenuAction.StatusFlags.Normal : ContextualMenu.MenuAction.StatusFlags.Hidden);
|
|
evt.menu.AppendAction("Show Generated Code", ShowGeneratedCode, node.hasPreview ? ContextualMenu.MenuAction.StatusFlags.Normal : ContextualMenu.MenuAction.StatusFlags.Hidden);
|
|
}
|
|
|
|
base.BuildContextualMenu(evt);
|
|
}
|
|
|
|
void CopyToClipboard()
|
|
{
|
|
GUIUtility.systemCopyBuffer = ConvertToShader();
|
|
}
|
|
|
|
public string SanitizeName(string name)
|
|
{
|
|
return new string(name.Where(c => !Char.IsWhiteSpace(c)).ToArray());
|
|
}
|
|
|
|
public void ShowGeneratedCode()
|
|
{
|
|
var graph = (AbstractMaterialGraph)node.owner;
|
|
|
|
string path = String.Format("Temp/GeneratedFromGraph-{0}-{1}-{2}.shader", SanitizeName(graph.name), SanitizeName(node.name), node.guid);
|
|
|
|
if (GraphUtil.WriteToFile(path, ConvertToShader()))
|
|
GraphUtil.OpenFile(path);
|
|
}
|
|
|
|
string ConvertToShader()
|
|
{
|
|
List<PropertyCollector.TextureInfo> textureInfo;
|
|
var masterNode = node as IMasterNode;
|
|
if (masterNode != null)
|
|
return masterNode.GetShader(GenerationMode.ForReals, node.name, out textureInfo);
|
|
|
|
var graph = (AbstractMaterialGraph)node.owner;
|
|
return graph.GetShader(node, GenerationMode.ForReals, node.name).shader;
|
|
}
|
|
|
|
void UpdateSettingsExpandedState()
|
|
{
|
|
m_ShowSettings = !m_ShowSettings;
|
|
if (m_ShowSettings)
|
|
{
|
|
m_NodeSettingsView.Add(m_Settings);
|
|
m_NodeSettingsView.visible = true;
|
|
|
|
m_SettingsButton.AddToClassList("clicked");
|
|
}
|
|
else
|
|
{
|
|
m_Settings.RemoveFromHierarchy();
|
|
|
|
m_NodeSettingsView.visible = false;
|
|
m_SettingsButton.RemoveFromClassList("clicked");
|
|
}
|
|
}
|
|
|
|
void UpdatePreviewExpandedState(bool expanded)
|
|
{
|
|
node.previewExpanded = expanded;
|
|
if (m_PreviewFiller == null)
|
|
return;
|
|
if (expanded)
|
|
{
|
|
if (m_PreviewContainer.parent != this)
|
|
{
|
|
Add(m_PreviewContainer);
|
|
m_PreviewContainer.PlaceBehind(this.Q("selection-border"));
|
|
}
|
|
m_PreviewFiller.AddToClassList("expanded");
|
|
m_PreviewFiller.RemoveFromClassList("collapsed");
|
|
}
|
|
else
|
|
{
|
|
if (m_PreviewContainer.parent == m_PreviewFiller)
|
|
{
|
|
m_PreviewContainer.RemoveFromHierarchy();
|
|
}
|
|
m_PreviewFiller.RemoveFromClassList("expanded");
|
|
m_PreviewFiller.AddToClassList("collapsed");
|
|
}
|
|
}
|
|
|
|
void UpdateTitle()
|
|
{
|
|
var subGraphNode = node as SubGraphNode;
|
|
if (subGraphNode != null && subGraphNode.subGraphAsset != null)
|
|
title = subGraphNode.subGraphAsset.name + " (sub)";
|
|
else
|
|
title = node.name;
|
|
}
|
|
|
|
public void OnModified(ModificationScope scope)
|
|
{
|
|
UpdateTitle();
|
|
if (node.hasPreview)
|
|
UpdatePreviewExpandedState(node.previewExpanded);
|
|
|
|
base.expanded = node.drawState.expanded;
|
|
|
|
// Update slots to match node modification
|
|
if (scope == ModificationScope.Topological)
|
|
{
|
|
var slots = node.GetSlots<MaterialSlot>().ToList();
|
|
|
|
var inputPorts = inputContainer.Children().OfType<ShaderPort>().ToList();
|
|
foreach (var port in inputPorts)
|
|
{
|
|
var currentSlot = port.slot;
|
|
var newSlot = slots.FirstOrDefault(s => s.id == currentSlot.id);
|
|
if (newSlot == null)
|
|
{
|
|
// Slot doesn't exist anymore, remove it
|
|
inputContainer.Remove(port);
|
|
|
|
// We also need to remove the inline input
|
|
var portInputView = m_PortInputContainer.OfType<PortInputView>().FirstOrDefault(v => Equals(v.slot, port.slot));
|
|
if (portInputView != null)
|
|
portInputView.RemoveFromHierarchy();
|
|
}
|
|
else
|
|
{
|
|
port.slot = newSlot;
|
|
var portInputView = m_PortInputContainer.OfType<PortInputView>().FirstOrDefault(x => x.slot.id == currentSlot.id);
|
|
portInputView.UpdateSlot(newSlot);
|
|
|
|
slots.Remove(newSlot);
|
|
}
|
|
}
|
|
|
|
var outputPorts = outputContainer.Children().OfType<ShaderPort>().ToList();
|
|
foreach (var port in outputPorts)
|
|
{
|
|
var currentSlot = port.slot;
|
|
var newSlot = slots.FirstOrDefault(s => s.id == currentSlot.id);
|
|
if (newSlot == null)
|
|
{
|
|
outputContainer.Remove(port);
|
|
}
|
|
else
|
|
{
|
|
port.slot = newSlot;
|
|
slots.Remove(newSlot);
|
|
}
|
|
}
|
|
|
|
AddSlots(slots);
|
|
|
|
slots.Clear();
|
|
slots.AddRange(node.GetSlots<MaterialSlot>());
|
|
|
|
if (inputContainer.childCount > 0)
|
|
inputContainer.Sort((x, y) => slots.IndexOf(((ShaderPort)x).slot) - slots.IndexOf(((ShaderPort)y).slot));
|
|
if (outputContainer.childCount > 0)
|
|
outputContainer.Sort((x, y) => slots.IndexOf(((ShaderPort)x).slot) - slots.IndexOf(((ShaderPort)y).slot));
|
|
}
|
|
|
|
RefreshExpandedState(); //This should not be needed. GraphView needs to improve the extension api here
|
|
UpdatePortInputs();
|
|
UpdatePortInputVisibilities();
|
|
|
|
foreach (var control in m_ControlItems)
|
|
{
|
|
var listener = control as INodeModificationListener;
|
|
if (listener != null)
|
|
listener.OnNodeModified(scope);
|
|
}
|
|
}
|
|
|
|
void AddSlots(IEnumerable<MaterialSlot> slots)
|
|
{
|
|
foreach (var slot in slots)
|
|
{
|
|
if (slot.hidden)
|
|
continue;
|
|
|
|
var port = ShaderPort.Create(slot, m_ConnectorListener);
|
|
if (slot.isOutputSlot)
|
|
outputContainer.Add(port);
|
|
else
|
|
inputContainer.Add(port);
|
|
}
|
|
}
|
|
|
|
void UpdatePortInputs()
|
|
{
|
|
foreach (var port in inputContainer.OfType<ShaderPort>())
|
|
{
|
|
if (!m_PortInputContainer.OfType<PortInputView>().Any(a => Equals(a.slot, port.slot)))
|
|
{
|
|
var portInputView = new PortInputView(port.slot) { style = { positionType = PositionType.Absolute } };
|
|
m_PortInputContainer.Add(portInputView);
|
|
port.RegisterCallback<GeometryChangedEvent>(evt => UpdatePortInput((ShaderPort)evt.target));
|
|
}
|
|
}
|
|
}
|
|
|
|
void UpdatePortInput(ShaderPort port)
|
|
{
|
|
var inputView = m_PortInputContainer.OfType<PortInputView>().First(x => Equals(x.slot, port.slot));
|
|
|
|
var currentRect = new Rect(inputView.style.positionLeft, inputView.style.positionTop, inputView.style.width, inputView.style.height);
|
|
var targetRect = new Rect(0.0f, 0.0f, port.layout.width, port.layout.height);
|
|
targetRect = port.ChangeCoordinatesTo(inputView.shadow.parent, targetRect);
|
|
var centerY = targetRect.center.y;
|
|
var centerX = targetRect.xMax - currentRect.width;
|
|
currentRect.center = new Vector2(centerX, centerY);
|
|
|
|
inputView.style.positionTop = currentRect.yMin;
|
|
var newHeight = inputView.parent.layout.height;
|
|
foreach (var element in inputView.parent.Children())
|
|
newHeight = Mathf.Max(newHeight, element.style.positionTop + element.layout.height);
|
|
if (Math.Abs(inputView.parent.style.height - newHeight) > 1e-3)
|
|
inputView.parent.style.height = newHeight;
|
|
}
|
|
|
|
public void UpdatePortInputVisibilities()
|
|
{
|
|
foreach (var portInputView in m_PortInputContainer.OfType<PortInputView>())
|
|
{
|
|
var slot = portInputView.slot;
|
|
var oldVisibility = portInputView.visible;
|
|
portInputView.visible = expanded && !node.owner.GetEdges(node.GetSlotReference(slot.id)).Any();
|
|
if (portInputView.visible != oldVisibility)
|
|
m_PortInputContainer.MarkDirtyRepaint();
|
|
}
|
|
}
|
|
|
|
public void UpdatePortInputTypes()
|
|
{
|
|
foreach (var anchor in inputContainer.Concat(outputContainer).OfType<ShaderPort>())
|
|
{
|
|
var slot = anchor.slot;
|
|
anchor.portName = slot.displayName;
|
|
anchor.visualClass = slot.concreteValueType.ToClassName();
|
|
}
|
|
|
|
foreach (var portInputView in m_PortInputContainer.OfType<PortInputView>())
|
|
portInputView.UpdateSlotType();
|
|
|
|
foreach (var control in m_ControlItems)
|
|
{
|
|
var listener = control as INodeModificationListener;
|
|
if (listener != null)
|
|
listener.OnNodeModified(ModificationScope.Graph);
|
|
}
|
|
}
|
|
|
|
void OnResize(Vector2 deltaSize)
|
|
{
|
|
var updatedWidth = topContainer.layout.width + deltaSize.x;
|
|
var updatedHeight = m_PreviewImage.layout.height + deltaSize.y;
|
|
|
|
var previewNode = node as PreviewNode;
|
|
if (previewNode != null)
|
|
{
|
|
previewNode.SetDimensions(updatedWidth, updatedHeight);
|
|
UpdateSize();
|
|
}
|
|
}
|
|
|
|
void UpdatePreviewTexture()
|
|
{
|
|
if (m_PreviewRenderData.texture == null || !node.previewExpanded)
|
|
{
|
|
m_PreviewImage.visible = false;
|
|
m_PreviewImage.image = Texture2D.blackTexture;
|
|
}
|
|
else
|
|
{
|
|
m_PreviewImage.visible = true;
|
|
m_PreviewImage.AddToClassList("visible");
|
|
m_PreviewImage.RemoveFromClassList("hidden");
|
|
if (m_PreviewImage.image != m_PreviewRenderData.texture)
|
|
m_PreviewImage.image = m_PreviewRenderData.texture;
|
|
else
|
|
m_PreviewImage.MarkDirtyRepaint();
|
|
}
|
|
}
|
|
|
|
void UpdateSize()
|
|
{
|
|
var previewNode = node as PreviewNode;
|
|
|
|
if (previewNode == null)
|
|
return;
|
|
|
|
var width = previewNode.width;
|
|
var height = previewNode.height;
|
|
|
|
m_PreviewImage.style.height = height;
|
|
m_PreviewImage.style.width = width;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
foreach (var portInputView in m_PortInputContainer.OfType<PortInputView>())
|
|
portInputView.Dispose();
|
|
|
|
node = null;
|
|
if (m_PreviewRenderData != null)
|
|
{
|
|
m_PreviewRenderData.onPreviewChanged -= UpdatePreviewTexture;
|
|
m_PreviewRenderData = null;
|
|
}
|
|
}
|
|
}
|
|
}
|