using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using UnityEditor.Graphing; using UnityEditor.Graphing.Util; using UnityEditor.ShaderGraph.Drawing.Controls; using UnityEditor.ShaderGraph.Internal; using UnityEditor.UIElements; using UnityEditorInternal; using UnityEngine; using UnityEngine.UIElements; using FloatField = UnityEditor.ShaderGraph.Drawing.FloatField; using ContextualMenuManipulator = UnityEngine.UIElements.ContextualMenuManipulator; using GraphDataStore = UnityEditor.ShaderGraph.DataStore; namespace UnityEditor.ShaderGraph.Drawing.Inspector.PropertyDrawers { [SGPropertyDrawer(typeof(ShaderInput))] class ShaderInputPropertyDrawer : IPropertyDrawer { internal delegate void ChangeExposedFieldCallback(bool newValue); internal delegate void ChangeValueCallback(object newValue); internal delegate void PreChangeValueCallback(string actionName); internal delegate void PostChangeValueCallback(bool bTriggerPropertyUpdate = false, ModificationScope modificationScope = ModificationScope.Node); // Keyword ReorderableList m_KeywordReorderableList; int m_KeywordSelectedIndex; // Dropdown ReorderableList m_DropdownReorderableList; ShaderDropdown m_Dropdown; int m_DropdownId; int m_DropdownSelectedIndex; //Virtual Texture ReorderableList m_VTReorderableList; int m_VTSelectedIndex; private static GUIStyle greyLabel; TextField m_VTLayer_Name; IdentifierField m_VTLayer_RefName; ObjectField m_VTLayer_Texture; EnumField m_VTLayer_TextureType; // Display Name TextField m_DisplayNameField; TextField m_CustomSlotLabelField; // Reference Name TextPropertyDrawer m_ReferenceNameDrawer; TextField m_ReferenceNameField; ShaderInput shaderInput; Toggle exposedToggle; VisualElement keywordScopeField; // Should be provided by the Inspectable ShaderInputViewModel m_ViewModel; ShaderInputViewModel ViewModel => m_ViewModel; const string m_DisplayNameDisallowedPattern = "[^\\w_ ]"; const string m_ReferenceNameDisallowedPattern = @"(?:[^A-Za-z_0-9_])"; public ShaderInputPropertyDrawer() { greyLabel = new GUIStyle(EditorStyles.label); greyLabel.normal = new GUIStyleState { textColor = Color.grey }; greyLabel.focused = new GUIStyleState { textColor = Color.grey }; greyLabel.hover = new GUIStyleState { textColor = Color.grey }; } GraphData graphData; bool isSubGraph { get; set; } ChangeExposedFieldCallback _exposedFieldChangedCallback; Action _precisionChangedCallback; Action _keywordChangedCallback; Action _dropdownChangedCallback; Action _displayNameChangedCallback; Action _referenceNameChangedCallback; ChangeValueCallback _changeValueCallback; PreChangeValueCallback _preChangeValueCallback; PostChangeValueCallback _postChangeValueCallback; internal void GetViewModel(ShaderInputViewModel shaderInputViewModel, GraphData inGraphData, PostChangeValueCallback postChangeValueCallback) { m_ViewModel = shaderInputViewModel; this.isSubGraph = m_ViewModel.isSubGraph; this.graphData = inGraphData; this._keywordChangedCallback = () => graphData.OnKeywordChanged(); this._dropdownChangedCallback = () => graphData.OnDropdownChanged(); this._precisionChangedCallback = () => graphData.ValidateGraph(); this._exposedFieldChangedCallback = newValue => { var changeExposedFlagAction = new ChangeExposedFlagAction(shaderInput, newValue); ViewModel.requestModelChangeAction(changeExposedFlagAction); }; this._displayNameChangedCallback = newValue => { var changeDisplayNameAction = new ChangeDisplayNameAction(); changeDisplayNameAction.shaderInputReference = shaderInput; changeDisplayNameAction.newDisplayNameValue = newValue; ViewModel.requestModelChangeAction(changeDisplayNameAction); }; this._changeValueCallback = newValue => { var changeDisplayNameAction = new ChangePropertyValueAction(); changeDisplayNameAction.shaderInputReference = shaderInput; changeDisplayNameAction.newShaderInputValue = newValue; ViewModel.requestModelChangeAction(changeDisplayNameAction); }; this._referenceNameChangedCallback = newValue => { var changeReferenceNameAction = new ChangeReferenceNameAction(); changeReferenceNameAction.shaderInputReference = shaderInput; changeReferenceNameAction.newReferenceNameValue = newValue; ViewModel.requestModelChangeAction(changeReferenceNameAction); }; this._preChangeValueCallback = (actionName) => this.graphData.owner.RegisterCompleteObjectUndo(actionName); if (shaderInput is AbstractShaderProperty abstractShaderProperty) { var changePropertyValueAction = new ChangePropertyValueAction(); changePropertyValueAction.shaderInputReference = abstractShaderProperty; this._changeValueCallback = newValue => { changePropertyValueAction.newShaderInputValue = newValue; ViewModel.requestModelChangeAction(changePropertyValueAction); }; } this._postChangeValueCallback = postChangeValueCallback; } public Action inspectorUpdateDelegate { get; set; } public VisualElement DrawProperty( PropertyInfo propertyInfo, object actualObject, InspectableAttribute attribute) { var propertySheet = new PropertySheet(); shaderInput = actualObject as ShaderInput; BuildPropertyNameLabel(propertySheet); BuildDisplayNameField(propertySheet); BuildReferenceNameField(propertySheet); BuildPropertyFields(propertySheet); BuildKeywordFields(propertySheet, shaderInput); BuildDropdownFields(propertySheet, shaderInput); UpdateEnableState(); return propertySheet; } void BuildPropertyNameLabel(PropertySheet propertySheet) { string prefix; if (shaderInput is ShaderKeyword) prefix = "Keyword"; else if (shaderInput is ShaderDropdown) prefix = "Dropdown"; else prefix = "Property"; propertySheet.headerContainer.Add(PropertyDrawerUtils.CreateLabel($"{prefix}: {shaderInput.displayName}", 0, FontStyle.Bold)); } void BuildExposedField(PropertySheet propertySheet) { if (!isSubGraph) { var toggleDataPropertyDrawer = new ToggleDataPropertyDrawer(); propertySheet.Add(toggleDataPropertyDrawer.CreateGUI( evt => { this._preChangeValueCallback("Change Exposed Toggle"); this._exposedFieldChangedCallback(evt.isOn); this._postChangeValueCallback(false, ModificationScope.Graph); }, new ToggleData(shaderInput.isExposed), "Exposed", out var exposedToggleVisualElement)); exposedToggle = exposedToggleVisualElement as Toggle; } } void BuildCustomBindingField(PropertySheet propertySheet, ShaderInput property) { if (isSubGraph && property.isCustomSlotAllowed) { var toggleDataPropertyDrawer = new ToggleDataPropertyDrawer(); propertySheet.Add(toggleDataPropertyDrawer.CreateGUI( newValue => { if (property.useCustomSlotLabel == newValue.isOn) return; this._preChangeValueCallback("Change Custom Binding"); property.useCustomSlotLabel = newValue.isOn; graphData.ValidateGraph(); this._postChangeValueCallback(true, ModificationScope.Topological); }, new ToggleData(property.isConnectionTestable), "Use Custom Binding", out var exposedToggleVisualElement)); exposedToggleVisualElement.SetEnabled(true); if (property.useCustomSlotLabel) { var textPropertyDrawer = new TextPropertyDrawer(); var guiElement = textPropertyDrawer.CreateGUI( null, (string)shaderInput.customSlotLabel, "Label", 1); m_CustomSlotLabelField = textPropertyDrawer.textField; m_CustomSlotLabelField.RegisterValueChangedCallback( evt => { if (evt.newValue != shaderInput.customSlotLabel) { this._preChangeValueCallback("Change Custom Binding Label"); shaderInput.customSlotLabel = evt.newValue; m_CustomSlotLabelField.AddToClassList("modified"); this._postChangeValueCallback(true, ModificationScope.Topological); } }); if (!string.IsNullOrEmpty(shaderInput.customSlotLabel)) m_CustomSlotLabelField.AddToClassList("modified"); m_CustomSlotLabelField.styleSheets.Add(Resources.Load("Styles/CustomSlotLabelField")); propertySheet.Add(guiElement); } } } void UpdateEnableState() { // some changes may change the exposed state exposedToggle?.SetValueWithoutNotify(shaderInput.isExposed); exposedToggle?.SetEnabled(shaderInput.isExposable && !shaderInput.isAlwaysExposed); if (shaderInput is ShaderKeyword keyword) { keywordScopeField?.SetEnabled(!keyword.isBuiltIn && (keyword.keywordDefinition != KeywordDefinition.Predefined)); this._exposedFieldChangedCallback(keyword.generatePropertyBlock); // change exposed icon appropriately } } void BuildDisplayNameField(PropertySheet propertySheet) { var textPropertyDrawer = new TextPropertyDrawer(); propertySheet.Add(textPropertyDrawer.CreateGUI( null, (string)shaderInput.displayName, "Name")); m_DisplayNameField = textPropertyDrawer.textField; m_DisplayNameField.RegisterValueChangedCallback( evt => { if (evt.newValue != shaderInput.displayName) { this._preChangeValueCallback("Change Display Name"); shaderInput.SetDisplayNameAndSanitizeForGraph(graphData, evt.newValue); this._displayNameChangedCallback(evt.newValue); if (string.IsNullOrEmpty(shaderInput.displayName)) m_DisplayNameField.RemoveFromClassList("modified"); else m_DisplayNameField.AddToClassList("modified"); this._postChangeValueCallback(true, ModificationScope.Topological); } }); if (!string.IsNullOrEmpty(shaderInput.displayName)) m_DisplayNameField.AddToClassList("modified"); m_DisplayNameField.SetEnabled(shaderInput.isRenamable); m_DisplayNameField.styleSheets.Add(Resources.Load("Styles/PropertyNameReferenceField")); } void BuildReferenceNameField(PropertySheet propertySheet) { if (!isSubGraph || shaderInput is ShaderKeyword) { m_ReferenceNameDrawer = new TextPropertyDrawer(); propertySheet.Add(m_ReferenceNameDrawer.CreateGUI( null, (string)shaderInput.referenceNameForEditing, "Reference")); m_ReferenceNameField = m_ReferenceNameDrawer.textField; m_ReferenceNameField.RegisterValueChangedCallback( evt => { this._preChangeValueCallback("Change Reference Name"); if (evt.newValue != shaderInput.referenceName) { shaderInput.SetReferenceNameAndSanitizeForGraph(graphData, evt.newValue); this._referenceNameChangedCallback(evt.newValue); } if (string.IsNullOrEmpty(shaderInput.overrideReferenceName)) { m_ReferenceNameField.RemoveFromClassList("modified"); m_ReferenceNameDrawer.label.RemoveFromClassList("modified"); } else { m_ReferenceNameField.AddToClassList("modified"); m_ReferenceNameDrawer.label.AddToClassList("modified"); } this._postChangeValueCallback(true, ModificationScope.Graph); }); if (!string.IsNullOrEmpty(shaderInput.overrideReferenceName)) { m_ReferenceNameDrawer.textField.AddToClassList("modified"); m_ReferenceNameDrawer.label.AddToClassList("modified"); } m_ReferenceNameDrawer.textField.SetEnabled(shaderInput.isReferenceRenamable); // add the right click context menu to the label IManipulator contextMenuManipulator = new ContextualMenuManipulator((evt) => AddShaderInputOptionsToContextMenu(shaderInput, evt)); m_ReferenceNameDrawer.label.AddManipulator(contextMenuManipulator); } } void AddShaderInputOptionsToContextMenu(ShaderInput shaderInput, ContextualMenuPopulateEvent evt) { if (shaderInput.isRenamable && !string.IsNullOrEmpty(shaderInput.overrideReferenceName)) evt.menu.AppendAction( "Reset Reference", e => { ResetReferenceName(); }, DropdownMenuAction.AlwaysEnabled); if (shaderInput.IsUsingOldDefaultRefName()) evt.menu.AppendAction( "Upgrade To New Reference Name", e => { UpgradeDefaultReferenceName(); }, DropdownMenuAction.AlwaysEnabled); } public void ResetReferenceName() { this._preChangeValueCallback("Reset Reference Name"); var refName = shaderInput.ResetReferenceName(graphData); m_ReferenceNameField.value = refName; this._referenceNameChangedCallback(refName); this._postChangeValueCallback(true, ModificationScope.Graph); } public void UpgradeDefaultReferenceName() { this._preChangeValueCallback("Upgrade Reference Name"); var refName = shaderInput.UpgradeDefaultReferenceName(graphData); m_ReferenceNameField.value = refName; this._referenceNameChangedCallback(refName); this._postChangeValueCallback(true, ModificationScope.Graph); } void BuildPropertyFields(PropertySheet propertySheet) { if (shaderInput is AbstractShaderProperty property) { if (property.sgVersion < property.latestVersion) { var typeString = property.propertyType.ToString(); var help = HelpBoxRow.TryGetDeprecatedHelpBoxRow($"{typeString} Property", () => property.ChangeVersion(property.latestVersion)); if (help != null) { propertySheet.Insert(0, help); } } switch (property) { case IShaderPropertyDrawer propDrawer: propDrawer.HandlePropertyField(propertySheet, _preChangeValueCallback, _postChangeValueCallback); break; case UnityEditor.ShaderGraph.Serialization.MultiJsonInternal.UnknownShaderPropertyType unknownProperty: var helpBox = new HelpBoxRow(MessageType.Warning); helpBox.Add(new Label("Cannot find the code for this Property, a package may be missing.")); propertySheet.Add(helpBox); break; case Vector1ShaderProperty vector1Property: HandleVector1ShaderProperty(propertySheet, vector1Property); break; case Vector2ShaderProperty vector2Property: HandleVector2ShaderProperty(propertySheet, vector2Property); break; case Vector3ShaderProperty vector3Property: HandleVector3ShaderProperty(propertySheet, vector3Property); break; case Vector4ShaderProperty vector4Property: HandleVector4ShaderProperty(propertySheet, vector4Property); break; case ColorShaderProperty colorProperty: HandleColorProperty(propertySheet, colorProperty); break; case Texture2DShaderProperty texture2DProperty: HandleTexture2DProperty(propertySheet, texture2DProperty); break; case Texture2DArrayShaderProperty texture2DArrayProperty: HandleTexture2DArrayProperty(propertySheet, texture2DArrayProperty); break; case VirtualTextureShaderProperty virtualTextureProperty: HandleVirtualTextureProperty(propertySheet, virtualTextureProperty); break; case Texture3DShaderProperty texture3DProperty: HandleTexture3DProperty(propertySheet, texture3DProperty); break; case CubemapShaderProperty cubemapProperty: HandleCubemapProperty(propertySheet, cubemapProperty); break; case BooleanShaderProperty booleanProperty: HandleBooleanProperty(propertySheet, booleanProperty); break; case Matrix2ShaderProperty matrix2Property: HandleMatrix2PropertyField(propertySheet, matrix2Property); break; case Matrix3ShaderProperty matrix3Property: HandleMatrix3PropertyField(propertySheet, matrix3Property); break; case Matrix4ShaderProperty matrix4Property: HandleMatrix4PropertyField(propertySheet, matrix4Property); break; case SamplerStateShaderProperty samplerStateProperty: HandleSamplerStatePropertyField(propertySheet, samplerStateProperty); break; case GradientShaderProperty gradientProperty: HandleGradientPropertyField(propertySheet, gradientProperty); break; } BuildPrecisionField(propertySheet, property); BuildExposedField(propertySheet); BuildHLSLDeclarationOverrideFields(propertySheet, property); } BuildCustomBindingField(propertySheet, shaderInput); } static string[] allHLSLDeclarationStrings = new string[] { "Do Not Declare", // HLSLDeclaration.DoNotDeclare "Global", // HLSLDeclaration.Global "Per Material", // HLSLDeclaration.UnityPerMaterial "Hybrid Per Instance", // HLSLDeclaration.HybridPerInstance }; void BuildHLSLDeclarationOverrideFields(PropertySheet propertySheet, AbstractShaderProperty property) { var hlslDecls = Enum.GetValues(typeof(HLSLDeclaration)); var allowedDecls = new List(); bool anyAllowed = false; for (int i = 0; i < hlslDecls.Length; i++) { HLSLDeclaration decl = (HLSLDeclaration)hlslDecls.GetValue(i); var allowed = property.AllowHLSLDeclaration(decl); anyAllowed = anyAllowed || allowed; if (allowed) allowedDecls.Add(decl); } if (anyAllowed) { var propRow = new PropertyRow(PropertyDrawerUtils.CreateLabel("Shader Declaration", 1)); var popupField = new PopupField( allowedDecls, property.GetDefaultHLSLDeclaration(), (h => allHLSLDeclarationStrings[(int)h]), (h => allHLSLDeclarationStrings[(int)h])); popupField.RegisterValueChangedCallback( evt => { this._preChangeValueCallback("Change Override"); if (property.hlslDeclarationOverride == evt.newValue) return; property.hlslDeclarationOverride = evt.newValue; this._postChangeValueCallback(); }); propRow.Add(popupField); var toggleOverride = new ToggleDataPropertyDrawer(); propertySheet.Add(toggleOverride.CreateGUI( newValue => { if (property.overrideHLSLDeclaration == newValue.isOn) return; this._preChangeValueCallback("Override Property Declaration"); // add or remove the sub field based on what the toggle is if (newValue.isOn) { // setup initial state based on current state property.hlslDeclarationOverride = property.GetDefaultHLSLDeclaration(); property.overrideHLSLDeclaration = newValue.isOn; popupField.value = property.hlslDeclarationOverride; propertySheet.Add(propRow); } else { property.overrideHLSLDeclaration = newValue.isOn; propRow.RemoveFromHierarchy(); } this._postChangeValueCallback(false, ModificationScope.Graph); }, new ToggleData(property.overrideHLSLDeclaration), "Override Property Declaration", out var overrideToggle)); // set up initial state overrideToggle.SetEnabled(anyAllowed); if (property.overrideHLSLDeclaration) propertySheet.Add(propRow); } } void BuildPrecisionField(PropertySheet propertySheet, AbstractShaderProperty property) { var enumPropertyDrawer = new EnumPropertyDrawer(); propertySheet.Add(enumPropertyDrawer.CreateGUI(newValue => { this._preChangeValueCallback("Change Precision"); if (property.precision == (Precision)newValue) return; property.precision = (Precision)newValue; this._precisionChangedCallback(); this._postChangeValueCallback(); }, (PropertyDrawerUtils.UIPrecisionForShaderGraphs)property.precision, "Precision", PropertyDrawerUtils.UIPrecisionForShaderGraphs.Inherit, out var precisionField)); if (property is Serialization.MultiJsonInternal.UnknownShaderPropertyType) precisionField.SetEnabled(false); } void HandleVector1ShaderProperty(PropertySheet propertySheet, Vector1ShaderProperty vector1ShaderProperty) { // Handle vector 1 mode parameters switch (vector1ShaderProperty.floatType) { case FloatType.Slider: var floatPropertyDrawer = new FloatPropertyDrawer(); // Default field propertySheet.Add(floatPropertyDrawer.CreateGUI( newValue => { _preChangeValueCallback("Change Property Value"); _changeValueCallback(newValue); _postChangeValueCallback(); }, vector1ShaderProperty.value, "Default", out var propertyFloatField)); // Min field propertySheet.Add(floatPropertyDrawer.CreateGUI( newValue => { if (newValue > vector1ShaderProperty.rangeValues.y) propertySheet.warningContainer.Q