using System; using System.Linq; using UnityEditor.Experimental.GraphView; using UnityEditor.Graphing; using UnityEditor.Rendering; using UnityEditor.ShaderGraph.Drawing; using UnityEditor.ShaderGraph.Drawing.Controls; using UnityEditor.ShaderGraph.Internal; using UnityEngine; using UnityEngine.UIElements; using UnityEditor.ShaderGraph.Drawing.Inspector.PropertyDrawers; using ContextualMenuManipulator = UnityEngine.UIElements.ContextualMenuManipulator; namespace UnityEditor.ShaderGraph { sealed class PropertyNodeView : TokenNode, IShaderNodeView, IInspectable { static readonly Texture2D exposedIcon = Resources.Load("GraphView/Nodes/BlackboardFieldExposed"); // When the properties are changed, this delegate is used to trigger an update in the view that represents those properties Action m_propertyViewUpdateTrigger; Action m_ResetReferenceNameAction; public PropertyNodeView(PropertyNode node, EdgeConnectorListener edgeConnectorListener) : base(null, ShaderPort.Create(node.GetOutputSlots().First(), edgeConnectorListener)) { styleSheets.Add(Resources.Load("Styles/PropertyNodeView")); this.node = node; viewDataKey = node.objectId.ToString(); userData = node; // Getting the generatePropertyBlock property to see if it is exposed or not UpdateIcon(); // Setting the position of the node, otherwise it ends up in the center of the canvas SetPosition(new Rect(node.drawState.position.x, node.drawState.position.y, 0, 0)); // Removing the title label since it is not used and taking up space this.Q("title-label").RemoveFromHierarchy(); // Add disabled overlay Add(new VisualElement() { name = "disabledOverlay", pickingMode = PickingMode.Ignore }); // Update active state SetActive(node.isActive); // Registering the hovering callbacks for highlighting RegisterCallback(OnMouseHover); RegisterCallback(OnMouseHover); // add the right click context menu IManipulator contextMenuManipulator = new ContextualMenuManipulator(AddContextMenuOptions); this.AddManipulator(contextMenuManipulator); // Set callback association for display name updates property.displayNameUpdateTrigger += node.UpdateNodeDisplayName; } public Node gvNode => this; public AbstractMaterialNode node { get; } public VisualElement colorElement => null; public string inspectorTitle => $"{property.displayName} (Node)"; [Inspectable("ShaderInput", null)] AbstractShaderProperty property => (node as PropertyNode)?.property; public object GetObjectToInspect() { return property; } public void SupplyDataToPropertyDrawer(IPropertyDrawer propertyDrawer, Action inspectorUpdateDelegate) { if (propertyDrawer is ShaderInputPropertyDrawer shaderInputPropertyDrawer) { var propNode = node as PropertyNode; var graph = node.owner as GraphData; var shaderInputViewModel = new ShaderInputViewModel() { model = property, parentView = null, isSubGraph = graph.isSubGraph, isInputExposed = property.isExposed, inputName = property.displayName, inputTypeName = property.GetPropertyTypeString(), requestModelChangeAction = this.RequestModelChange }; shaderInputPropertyDrawer.GetViewModel(shaderInputViewModel, node.owner, this.MarkNodesAsDirty); this.m_propertyViewUpdateTrigger = inspectorUpdateDelegate; this.m_ResetReferenceNameAction = shaderInputPropertyDrawer.ResetReferenceName; } } void RequestModelChange(IGraphDataAction changeAction) { node.owner?.owner.graphDataStore.Dispatch(changeAction); } void ChangeExposedField(bool newValue) { property.generatePropertyBlock = newValue; UpdateIcon(); } void ChangeDisplayName(string newValue) { property.displayName = newValue; } internal static void AddMainColorMenuOptions(ContextualMenuPopulateEvent evt, ColorShaderProperty colorProp, GraphData graphData, Action inspectorUpdateAction) { if (!graphData.isSubGraph) { if (!colorProp.isMainColor) { evt.menu.AppendAction( "Set as Main Color", e => { ColorShaderProperty col = graphData.GetMainColor(); if (col != null) { if (EditorUtility.DisplayDialog("Change Main Color Action", $"Are you sure you want to change the Main Color from {col.displayName} to {colorProp.displayName}?", "Yes", "Cancel")) { graphData.owner.RegisterCompleteObjectUndo("Change Main Color"); col.isMainColor = false; colorProp.isMainColor = true; inspectorUpdateAction(); } return; } graphData.owner.RegisterCompleteObjectUndo("Set Main Color"); colorProp.isMainColor = true; inspectorUpdateAction(); }); } else { evt.menu.AppendAction( "Clear Main Color", e => { graphData.owner.RegisterCompleteObjectUndo("Clear Main Color"); colorProp.isMainColor = false; inspectorUpdateAction(); }); } } } internal static void AddMainTextureMenuOptions(ContextualMenuPopulateEvent evt, Texture2DShaderProperty texProp, GraphData graphData, Action inspectorUpdateAction) { if (!graphData.isSubGraph) { if (!texProp.isMainTexture) { evt.menu.AppendAction( "Set as Main Texture", e => { Texture2DShaderProperty tex = graphData.GetMainTexture(); // There's already a main texture, ask the user if they want to change and toggle the old one to not be main if (tex != null) { if (EditorUtility.DisplayDialog("Change Main Texture Action", $"Are you sure you want to change the Main Texture from {tex.displayName} to {texProp.displayName}?", "Yes", "Cancel")) { graphData.owner.RegisterCompleteObjectUndo("Change Main Texture"); tex.isMainTexture = false; texProp.isMainTexture = true; inspectorUpdateAction(); } return; } graphData.owner.RegisterCompleteObjectUndo("Set Main Texture"); texProp.isMainTexture = true; inspectorUpdateAction(); }); } else { evt.menu.AppendAction( "Clear Main Texture", e => { graphData.owner.RegisterCompleteObjectUndo("Clear Main Texture"); texProp.isMainTexture = false; inspectorUpdateAction(); }); } } } void AddContextMenuOptions(ContextualMenuPopulateEvent evt) { // Checks if the reference name has been overridden and appends menu action to reset it, if so if (property.isRenamable && !string.IsNullOrEmpty(property.overrideReferenceName)) { evt.menu.AppendAction( "Reset Reference", e => { m_ResetReferenceNameAction(); DirtyNodes(ModificationScope.Graph); }, DropdownMenuAction.AlwaysEnabled); } if (property is ColorShaderProperty colorProp) { AddMainColorMenuOptions(evt, colorProp, node.owner, m_propertyViewUpdateTrigger); } if (property is Texture2DShaderProperty texProp) { AddMainTextureMenuOptions(evt, texProp, node.owner, m_propertyViewUpdateTrigger); } } void RegisterPropertyChangeUndo(string actionName) { var graph = node.owner as GraphData; graph.owner.RegisterCompleteObjectUndo(actionName); } void MarkNodesAsDirty(bool triggerPropertyViewUpdate = false, ModificationScope modificationScope = ModificationScope.Node) { DirtyNodes(modificationScope); if (triggerPropertyViewUpdate) this.m_propertyViewUpdateTrigger(); } void ChangePropertyValue(object newValue) { if (property == null) return; switch (property) { case BooleanShaderProperty booleanProperty: booleanProperty.value = ((ToggleData)newValue).isOn; break; case Vector1ShaderProperty vector1Property: vector1Property.value = (float)newValue; break; case Vector2ShaderProperty vector2Property: vector2Property.value = (Vector2)newValue; break; case Vector3ShaderProperty vector3Property: vector3Property.value = (Vector3)newValue; break; case Vector4ShaderProperty vector4Property: vector4Property.value = (Vector4)newValue; break; case ColorShaderProperty colorProperty: colorProperty.value = (Color)newValue; break; case Texture2DShaderProperty texture2DProperty: texture2DProperty.value.texture = (Texture)newValue; break; case Texture2DArrayShaderProperty texture2DArrayProperty: texture2DArrayProperty.value.textureArray = (Texture2DArray)newValue; break; case Texture3DShaderProperty texture3DProperty: texture3DProperty.value.texture = (Texture3D)newValue; break; case CubemapShaderProperty cubemapProperty: cubemapProperty.value.cubemap = (Cubemap)newValue; break; case Matrix2ShaderProperty matrix2Property: matrix2Property.value = (Matrix4x4)newValue; break; case Matrix3ShaderProperty matrix3Property: matrix3Property.value = (Matrix4x4)newValue; break; case Matrix4ShaderProperty matrix4Property: matrix4Property.value = (Matrix4x4)newValue; break; case SamplerStateShaderProperty samplerStateProperty: samplerStateProperty.value = (TextureSamplerState)newValue; break; case GradientShaderProperty gradientProperty: gradientProperty.value = (Gradient)newValue; break; default: throw new ArgumentOutOfRangeException(); } this.MarkDirtyRepaint(); } void DirtyNodes(ModificationScope modificationScope = ModificationScope.Node) { var graph = node.owner as GraphData; var colorManager = GetFirstAncestorOfType().colorManager; var nodes = GetFirstAncestorOfType().graphView.Query().ToList(); colorManager.SetNodesDirty(nodes); colorManager.UpdateNodeViews(nodes); foreach (var node in graph.GetNodes()) { node.Dirty(modificationScope); } } public void SetColor(Color newColor) { // Nothing to do here yet } public void ResetColor() { // Nothing to do here yet } public void UpdatePortInputTypes() { } public void UpdateDropdownEntries() { } public bool FindPort(SlotReference slot, out ShaderPort port) { port = output as ShaderPort; return port != null && port.slot.slotReference.Equals(slot); } void UpdateIcon() { var graph = node?.owner as GraphData; if ((graph != null) && (property != null)) icon = (graph.isSubGraph || property.isExposed) ? exposedIcon : null; else icon = null; } public void OnModified(ModificationScope scope) { //disconnected property nodes are always active if (!node.IsSlotConnected(PropertyNode.OutputSlotId)) node.SetActive(true); SetActive(node.isActive); if (scope == ModificationScope.Graph) { UpdateIcon(); } if (scope == ModificationScope.Topological || scope == ModificationScope.Node) { // Updating the text label of the output slot var slot = node.GetSlots().ToList().First(); this.Q