using UnityEngine; using System; using System.Collections.Generic; using UnityEditor.Graphing; using UnityEditor.ShaderGraph.Drawing.Controls; using UnityEditor.ShaderGraph.Internal; using UnityEngine.Assertions; using UnityEngine.UIElements; using GraphDataStore = UnityEditor.ShaderGraph.DataStore; namespace UnityEditor.ShaderGraph.Drawing { class ChangeExposedFlagAction : IGraphDataAction { internal ChangeExposedFlagAction(ShaderInput shaderInput, bool newIsExposed) { this.shaderInputReference = shaderInput; this.newIsExposedValue = newIsExposed; this.oldIsExposedValue = shaderInput.generatePropertyBlock; } void ChangeExposedFlag(GraphData graphData) { AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out ChangeExposedFlagAction"); AssertHelpers.IsNotNull(shaderInputReference, "ShaderInputReference is null while carrying out ChangeExposedFlagAction"); // The Undos are currently handled in ShaderInputPropertyDrawer but we want to move that out from there and handle here //graphData.owner.RegisterCompleteObjectUndo("Change Exposed Toggle"); shaderInputReference.generatePropertyBlock = newIsExposedValue; } public Action modifyGraphDataAction => ChangeExposedFlag; // Reference to the shader input being modified internal ShaderInput shaderInputReference { get; private set; } // New value of whether the shader input should be exposed to the material inspector internal bool newIsExposedValue { get; private set; } internal bool oldIsExposedValue { get; private set; } } class ChangePropertyValueAction : IGraphDataAction { void ChangePropertyValue(GraphData graphData) { AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out ChangePropertyValueAction"); AssertHelpers.IsNotNull(shaderInputReference, "ShaderPropertyReference is null while carrying out ChangePropertyValueAction"); // The Undos are currently handled in ShaderInputPropertyDrawer but we want to move that out from there and handle here //graphData.owner.RegisterCompleteObjectUndo("Change Property Value"); var material = graphData.owner.materialArtifact; switch (shaderInputReference) { case BooleanShaderProperty booleanProperty: booleanProperty.value = ((ToggleData)newShaderInputValue).isOn; if (material) material.SetFloat(shaderInputReference.referenceName, booleanProperty.value ? 1.0f : 0.0f); break; case Vector1ShaderProperty vector1Property: vector1Property.value = (float)newShaderInputValue; if (material) material.SetFloat(shaderInputReference.referenceName, vector1Property.value); break; case Vector2ShaderProperty vector2Property: vector2Property.value = (Vector2)newShaderInputValue; if (material) material.SetVector(shaderInputReference.referenceName, vector2Property.value); break; case Vector3ShaderProperty vector3Property: vector3Property.value = (Vector3)newShaderInputValue; if (material) material.SetVector(shaderInputReference.referenceName, vector3Property.value); break; case Vector4ShaderProperty vector4Property: vector4Property.value = (Vector4)newShaderInputValue; if (material) material.SetVector(shaderInputReference.referenceName, vector4Property.value); break; case ColorShaderProperty colorProperty: colorProperty.value = (Color)newShaderInputValue; if (material) material.SetColor(shaderInputReference.referenceName, colorProperty.value); break; case Texture2DShaderProperty texture2DProperty: texture2DProperty.value.texture = (Texture)newShaderInputValue; if (material) material.SetTexture(shaderInputReference.referenceName, texture2DProperty.value.texture); break; case Texture2DArrayShaderProperty texture2DArrayProperty: texture2DArrayProperty.value.textureArray = (Texture2DArray)newShaderInputValue; if (material) material.SetTexture(shaderInputReference.referenceName, texture2DArrayProperty.value.textureArray); break; case Texture3DShaderProperty texture3DProperty: texture3DProperty.value.texture = (Texture3D)newShaderInputValue; if (material) material.SetTexture(shaderInputReference.referenceName, texture3DProperty.value.texture); break; case CubemapShaderProperty cubemapProperty: cubemapProperty.value.cubemap = (Cubemap)newShaderInputValue; if (material) material.SetTexture(shaderInputReference.referenceName, cubemapProperty.value.cubemap); break; case Matrix2ShaderProperty matrix2Property: matrix2Property.value = (Matrix4x4)newShaderInputValue; break; case Matrix3ShaderProperty matrix3Property: matrix3Property.value = (Matrix4x4)newShaderInputValue; break; case Matrix4ShaderProperty matrix4Property: matrix4Property.value = (Matrix4x4)newShaderInputValue; break; case SamplerStateShaderProperty samplerStateProperty: samplerStateProperty.value = (TextureSamplerState)newShaderInputValue; break; case GradientShaderProperty gradientProperty: gradientProperty.value = (Gradient)newShaderInputValue; break; default: throw new ArgumentOutOfRangeException(); } } public Action modifyGraphDataAction => ChangePropertyValue; // Reference to the shader input being modified internal ShaderInput shaderInputReference { get; set; } // New value of the shader property internal object newShaderInputValue { get; set; } } class ChangeDisplayNameAction : IGraphDataAction { void ChangeDisplayName(GraphData graphData) { AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out ChangeDisplayNameAction"); AssertHelpers.IsNotNull(shaderInputReference, "ShaderInputReference is null while carrying out ChangeDisplayNameAction"); graphData.owner.RegisterCompleteObjectUndo("Change Display Name"); if (newDisplayNameValue != shaderInputReference.displayName) { shaderInputReference.SetDisplayNameAndSanitizeForGraph(graphData, newDisplayNameValue); } } public Action modifyGraphDataAction => ChangeDisplayName; // Reference to the shader input being modified internal ShaderInput shaderInputReference { get; set; } internal string newDisplayNameValue { get; set; } } class ChangeReferenceNameAction : IGraphDataAction { void ChangeReferenceName(GraphData graphData) { AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out ChangeReferenceNameAction"); AssertHelpers.IsNotNull(shaderInputReference, "ShaderInputReference is null while carrying out ChangeReferenceNameAction"); // The Undos are currently handled in ShaderInputPropertyDrawer but we want to move that out from there and handle here //graphData.owner.RegisterCompleteObjectUndo("Change Reference Name"); if (newReferenceNameValue != shaderInputReference.overrideReferenceName) { graphData.SanitizeGraphInputReferenceName(shaderInputReference, newReferenceNameValue); } } public Action modifyGraphDataAction => ChangeReferenceName; // Reference to the shader input being modified internal ShaderInput shaderInputReference { get; set; } internal string newReferenceNameValue { get; set; } } class ResetReferenceNameAction : IGraphDataAction { void ResetReferenceName(GraphData graphData) { AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out ResetReferenceNameAction"); AssertHelpers.IsNotNull(shaderInputReference, "ShaderInputReference is null while carrying out ResetReferenceNameAction"); graphData.owner.RegisterCompleteObjectUndo("Reset Reference Name"); shaderInputReference.overrideReferenceName = null; } public Action modifyGraphDataAction => ResetReferenceName; // Reference to the shader input being modified internal ShaderInput shaderInputReference { get; set; } } class DeleteShaderInputAction : IGraphDataAction { void DeleteShaderInput(GraphData graphData) { AssertHelpers.IsNotNull(graphData, "GraphData is null while carrying out DeleteShaderInputAction"); AssertHelpers.IsNotNull(shaderInputsToDelete, "ShaderInputsToDelete is null while carrying out DeleteShaderInputAction"); // This is called by MaterialGraphView currently, no need to repeat it here, though ideally it would live here //graphData.owner.RegisterCompleteObjectUndo("Delete Graph Input(s)"); foreach (var shaderInput in shaderInputsToDelete) { graphData.RemoveGraphInput(shaderInput); } } public Action modifyGraphDataAction => DeleteShaderInput; // Reference to the shader input(s) being deleted internal IList shaderInputsToDelete { get; set; } = new List(); } class ShaderInputViewController : SGViewController { // Exposed for PropertyView internal GraphData graphData => DataStore.State; internal ShaderInputViewController(ShaderInput shaderInput, ShaderInputViewModel inViewModel, GraphDataStore graphDataStore) : base(shaderInput, inViewModel, graphDataStore) { InitializeViewModel(); m_SgBlackboardField = new SGBlackboardField(ViewModel); m_SgBlackboardField.controller = this; m_BlackboardRowView = new SGBlackboardRow(m_SgBlackboardField, null); m_BlackboardRowView.expanded = SessionState.GetBool($"Unity.ShaderGraph.Input.{shaderInput.objectId}.isExpanded", false); } void InitializeViewModel() { if (Model == null) { AssertHelpers.Fail("Could not initialize shader input view model as shader input was null."); return; } ViewModel.model = Model; ViewModel.isSubGraph = DataStore.State.isSubGraph; ViewModel.isInputExposed = (DataStore.State.isSubGraph || (Model.isExposable && Model.generatePropertyBlock)); ViewModel.inputName = Model.displayName; switch (Model) { case AbstractShaderProperty shaderProperty: ViewModel.inputTypeName = shaderProperty.GetPropertyTypeString(); // Handles upgrade fix for deprecated old Color property shaderProperty.onBeforeVersionChange += (_) => graphData.owner.RegisterCompleteObjectUndo($"Change {shaderProperty.displayName} Version"); break; case ShaderKeyword shaderKeyword: ViewModel.inputTypeName = shaderKeyword.keywordType + " Keyword"; ViewModel.inputTypeName = shaderKeyword.isBuiltIn ? "Built-in " + ViewModel.inputTypeName : ViewModel.inputTypeName; break; case ShaderDropdown shaderDropdown: ViewModel.inputTypeName = "Dropdown"; break; } ViewModel.requestModelChangeAction = this.RequestModelChange; } SGBlackboardRow m_BlackboardRowView; SGBlackboardField m_SgBlackboardField; internal SGBlackboardRow BlackboardItemView => m_BlackboardRowView; protected override void RequestModelChange(IGraphDataAction changeAction) { DataStore.Dispatch(changeAction); } // Called by GraphDataStore.Subscribe after the model has been changed protected override void ModelChanged(GraphData graphData, IGraphDataAction changeAction) { switch (changeAction) { case ChangeExposedFlagAction changeExposedFlagAction: // ModelChanged is called overzealously on everything // but we only care if the action pertains to our Model if (changeExposedFlagAction.shaderInputReference == Model) { ViewModel.isInputExposed = Model.generatePropertyBlock; if (changeExposedFlagAction.oldIsExposedValue != changeExposedFlagAction.newIsExposedValue) DirtyNodes(ModificationScope.Graph); m_SgBlackboardField.UpdateFromViewModel(); } break; case ChangePropertyValueAction changePropertyValueAction: if (changePropertyValueAction.shaderInputReference == Model) { DirtyNodes(ModificationScope.Graph); m_SgBlackboardField.MarkDirtyRepaint(); } break; case ResetReferenceNameAction resetReferenceNameAction: if (resetReferenceNameAction.shaderInputReference == Model) { DirtyNodes(ModificationScope.Graph); } break; case ChangeReferenceNameAction changeReferenceNameAction: if (changeReferenceNameAction.shaderInputReference == Model) { DirtyNodes(ModificationScope.Graph); } break; case ChangeDisplayNameAction changeDisplayNameAction: if (changeDisplayNameAction.shaderInputReference == Model) { ViewModel.inputName = Model.displayName; DirtyNodes(ModificationScope.Layout); m_SgBlackboardField.UpdateFromViewModel(); } break; } } // TODO: This should communicate to node controllers instead of searching for the nodes themselves everytime, but that's going to take a while... internal void DirtyNodes(ModificationScope modificationScope = ModificationScope.Node) { foreach (var observer in Model.InputObservers) { observer.OnShaderInputUpdated(modificationScope); } switch (Model) { case AbstractShaderProperty property: var graphEditorView = m_BlackboardRowView.GetFirstAncestorOfType(); if (graphEditorView == null) return; var colorManager = graphEditorView.colorManager; var nodes = graphEditorView.graphView.Query().ToList(); colorManager.SetNodesDirty(nodes); colorManager.UpdateNodeViews(nodes); break; case ShaderKeyword keyword: // Cant determine if Sub Graphs contain the keyword so just update them foreach (var node in DataStore.State.GetNodes()) { node.Dirty(modificationScope); } break; case ShaderDropdown dropdown: // Cant determine if Sub Graphs contain the dropdown so just update them foreach (var node in DataStore.State.GetNodes()) { node.Dirty(modificationScope); } break; default: throw new ArgumentOutOfRangeException(); } } public override void Dispose() { if (m_SgBlackboardField == null) return; Model?.ClearObservers(); base.Dispose(); Cleanup(); BlackboardItemView.Dispose(); m_SgBlackboardField.Dispose(); m_BlackboardRowView = null; m_SgBlackboardField = null; } } }