You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

2349 lines
107 KiB

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<UnityEditor.ShaderGraph.GraphData>;
using UnityEditor.Rendering;
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_])";
const string m_EnumRefDisallowedPattern = @"(?:[^A-Za-z_0-9_.])";
const string m_AttributeValueDisallowedPattern = @"(?:[^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<string> _displayNameChangedCallback;
Action<string> _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);
BuildPromoteField(propertySheet);
BuildPropertyFields(propertySheet);
BuildKeywordFields(propertySheet, shaderInput);
BuildDropdownFields(propertySheet, shaderInput);
BuildAttributesFields(propertySheet);
UpdateEnableState();
return propertySheet;
}
void IPropertyDrawer.DisposePropertyDrawer() { }
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 BuildPerRendererDataField(PropertySheet propertySheet)
{
if ((!isSubGraph || shaderInput.promoteToFinalShader) && shaderInput is AbstractShaderProperty property && property.isExposed)
{
var toggleDataPropertyDrawer = new ToggleDataPropertyDrawer();
propertySheet.Add(toggleDataPropertyDrawer.CreateGUI(
evt =>
{
this._preChangeValueCallback("Change PerRendererData Toggle");
property.PerRendererData = evt.isOn;
this._postChangeValueCallback(true, ModificationScope.Graph);
},
new ToggleData(property.PerRendererData),
"Read Only",
out var perRendererDataToggleVisualElement,
tooltip: "Mark this property with [PerRendererData] attribute.\nThis makes it Read-Only in the material inspector."));
//perRendererDataToggleVisualElement.SetEnabled(property.isExposed);
}
}
void BuildPromoteField(PropertySheet propertySheet)
{
if (!isSubGraph || !shaderInput.canPromoteToFinalShader)
return;
var toggleDataPropertyDrawer = new ToggleDataPropertyDrawer();
propertySheet.Add(toggleDataPropertyDrawer.CreateGUI(
evt =>
{
this._preChangeValueCallback("Change Promote to Shader toggle");
shaderInput.promotedFromAssetID = evt.isOn ? graphData.assetGuid : null;
this._postChangeValueCallback(true, ModificationScope.Graph);
},
new ToggleData(shaderInput.promoteToFinalShader),
"Promote to final Shader",
out var promoteToggleVisualElement,
tooltip: "Promote this as a material property to the final shader. It will not show up as an input port on the Subgraph Node."));
}
void BuildExposedField(PropertySheet propertySheet)
{
if (!isSubGraph || shaderInput.promoteToFinalShader)
{
var toggleDataPropertyDrawer = new ToggleDataPropertyDrawer();
propertySheet.Add(toggleDataPropertyDrawer.CreateGUI(
evt =>
{
if (shaderInput is AbstractShaderProperty property &&
(!property.overrideHLSLDeclaration || property.hlslDeclarationOverride == HLSLDeclaration.DoNotDeclare))
{
property.hlslDeclarationOverride = property.GetDefaultHLSLDeclaration();
property.overrideHLSLDeclaration = true;
}
this._preChangeValueCallback("Change Exposed Toggle");
this._exposedFieldChangedCallback(evt.isOn);
this._postChangeValueCallback(true, ModificationScope.Graph);
},
new ToggleData(shaderInput.isExposed),
shaderInput is ShaderKeyword ? "Generate Material Property" : "Show In Inspector",
out var exposedToggleVisualElement,
tooltip: shaderInput is ShaderKeyword ? "Generate a material property declaration to show this field in the material inspector." : "Hide or Show this property in the material inspector."));
exposedToggle = exposedToggleVisualElement as Toggle;
}
}
void BuildCustomBindingField(PropertySheet propertySheet, ShaderInput property)
{
if (isSubGraph && property.isCustomSlotAllowed && !shaderInput.promoteToFinalShader)
{
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<StyleSheet>("Styles/CustomSlotLabelField"));
propertySheet.Add(guiElement);
}
}
}
void UpdateEnableState()
{
// some changes may change the exposed state
exposedToggle?.SetValueWithoutNotify(shaderInput.isExposed);
if (shaderInput is ShaderKeyword keyword)
{
keywordScopeField?.SetEnabled(!keyword.isBuiltIn && (keyword.keywordDefinition != KeywordDefinition.Predefined));
exposedToggle?.SetEnabled((keyword.keywordDefinition != KeywordDefinition.Predefined));
this._exposedFieldChangedCallback(keyword.generatePropertyBlock); // change exposed icon appropriately
}
else if (shaderInput is AbstractShaderProperty property)
{
// this is the best field to represent whether the toggle should be presented or not.
exposedToggle?.SetEnabled(property.shouldForceExposed);
}
else
{
exposedToggle?.SetEnabled(shaderInput.isExposable && !shaderInput.isAlwaysExposed);
}
}
void BuildDisplayNameField(PropertySheet propertySheet)
{
var textPropertyDrawer = new TextPropertyDrawer();
propertySheet.Add(textPropertyDrawer.CreateGUI(
null,
(string)shaderInput.displayName,
"Name",
tooltip: "Display name used in the material inspector."));
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.Layout);
}
});
if (!string.IsNullOrEmpty(shaderInput.displayName))
m_DisplayNameField.AddToClassList("modified");
m_DisplayNameField.SetEnabled(shaderInput.isRenamable);
m_DisplayNameField.styleSheets.Add(Resources.Load<StyleSheet>("Styles/PropertyNameReferenceField"));
}
void BuildReferenceNameField(PropertySheet propertySheet)
{
if ((!isSubGraph || shaderInput.promoteToFinalShader) || shaderInput is ShaderKeyword)
{
m_ReferenceNameDrawer = new TextPropertyDrawer();
propertySheet.Add(m_ReferenceNameDrawer.CreateGUI(
null,
(string)shaderInput.referenceNameForEditing,
"Reference",
tooltip: "HLSL identifier used in the generated shader code. Use this with the material scripting API."));
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);
}
bool isCurrentPropertyGlobal;
void BuildPropertyFields(PropertySheet propertySheet)
{
if (shaderInput is AbstractShaderProperty property)
{
if (property.sgVersion < property.latestVersion)
{
var typeString = property.propertyType.ToString();
Action dismissAction = null;
if (property.dismissedUpdateVersion < property.latestVersion)
{
dismissAction = () =>
{
_preChangeValueCallback("Dismiss Property Update");
property.dismissedUpdateVersion = property.latestVersion;
_postChangeValueCallback();
inspectorUpdateDelegate?.Invoke();
};
}
var help = HelpBoxRow.CreateUpgradePrompt($"{typeString} Property",
() => property.ChangeVersion(property.latestVersion),
dismissAction);
if (help != null)
{
propertySheet.Insert(0, help);
}
}
isCurrentPropertyGlobal = property.GetDefaultHLSLDeclaration() == HLSLDeclaration.Global;
BuildPrecisionField(propertySheet, property);
BuildHLSLDeclarationOverrideFields(propertySheet, property);
BuildExposedField(propertySheet);
BuildPerRendererDataField(propertySheet);
switch (property)
{
case IShaderPropertyDrawer propDrawer:
propDrawer.HandlePropertyField(propertySheet, _preChangeValueCallback, _postChangeValueCallback);
break;
case UnityEditor.ShaderGraph.Serialization.MultiJsonInternal.UnknownShaderPropertyType:
var helpBox = new HelpBoxRow("Cannot find the code for this Property, a package may be missing.", MessageType.Warning);
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;
}
}
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)
{
if (isSubGraph && !shaderInput.promoteToFinalShader)
return;
var hlslDecls = Enum.GetValues(typeof(HLSLDeclaration));
var allowedDecls = new List<HLSLDeclaration>();
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);
}
const string tooltip = "Indicate where the property is expected to be changed.";
if (anyAllowed)
{
var propRow = new PropertyRow(PropertyDrawerUtils.CreateLabel("Scope", 0));
propRow.tooltip = tooltip;
var popupField = new PopupField<HLSLDeclaration>(
allowedDecls,
property.GetDefaultHLSLDeclaration(),
(h => allHLSLDeclarationStrings[(int)h]),
(h => allHLSLDeclarationStrings[(int)h]));
popupField.RegisterValueChangedCallback(
evt =>
{
this._preChangeValueCallback("Change Scope");
if (property.hlslDeclarationOverride == evt.newValue)
return;
property.hlslDeclarationOverride = evt.newValue;
property.overrideHLSLDeclaration = true;
UpdateEnableState();
if ((evt.newValue == HLSLDeclaration.Global) ^ (evt.previousValue == HLSLDeclaration.Global))
{
this._exposedFieldChangedCallback(evt.newValue != HLSLDeclaration.Global);
property.generatePropertyBlock = evt.newValue != HLSLDeclaration.Global;
}
this._postChangeValueCallback(true, ModificationScope.Graph);
});
propRow.Add(popupField);
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,
tooltip: "Request that the property uses Single 32-bit or Half 16-bit floating point precision."));
if (property is Serialization.MultiJsonInternal.UnknownShaderPropertyType)
precisionField.SetEnabled(false);
}
enum EnumTypeForUI { ExplicitValues = EnumType.Enum, TypeReference = EnumType.CSharpEnum }
void HandleVector1ShaderProperty(PropertySheet propertySheet, Vector1ShaderProperty vector1ShaderProperty)
{
if (shaderInput.isExposed && (!isSubGraph || shaderInput.promoteToFinalShader) && !isCurrentPropertyGlobal)
{
var enumPropertyDrawer = new EnumPropertyDrawer();
propertySheet.Add(enumPropertyDrawer.CreateGUI(
newValue =>
{
this._preChangeValueCallback("Change Vector1 Mode");
vector1ShaderProperty.floatType = (FloatType)newValue;
this._postChangeValueCallback(true);
},
vector1ShaderProperty.floatType,
"Mode",
FloatType.Default,
out var modePropertyEnumField,
tooltip: "Indicate how this float property should appear in the material inspector UI."));
}
var floatType = (!shaderInput.isExposed || isSubGraph || isCurrentPropertyGlobal) && !shaderInput.promoteToFinalShader ? FloatType.Default : vector1ShaderProperty.floatType;
// Handle vector 1 mode parameters
switch (floatType)
{
case FloatType.Slider:
var sliderTypePropertyDrawer = new EnumPropertyDrawer();
propertySheet.Add(sliderTypePropertyDrawer.CreateGUI(
newValue =>
{
this._preChangeValueCallback("Change Slider Type");
vector1ShaderProperty.sliderType = (SliderType)newValue;
this._postChangeValueCallback(true);
},
vector1ShaderProperty.sliderType,
"Slider Type",
SliderType.Default,
out var sliderTypePropertyEnumField,
tooltip: "Set the Slider type."));
var floatPropertyDrawer = new FloatPropertyDrawer();
// Default field
propertySheet.Add(floatPropertyDrawer.CreateGUI(
newValue =>
{
_preChangeValueCallback("Change Property Value");
_changeValueCallback(newValue);
_postChangeValueCallback();
},
vector1ShaderProperty.value,
isCurrentPropertyGlobal ? "Preview Value" : "Default Value",
out var propertyFloatField));
// Min field
propertySheet.Add(floatPropertyDrawer.CreateGUI(
newValue =>
{
if (newValue > vector1ShaderProperty.rangeValues.y)
propertySheet.warningContainer.Q<Label>().text = "Min cannot be greater than Max.";
_preChangeValueCallback("Change Range Property Minimum");
vector1ShaderProperty.rangeValues = new Vector2(newValue, vector1ShaderProperty.rangeValues.y);
_postChangeValueCallback();
},
vector1ShaderProperty.rangeValues.x,
"Min",
out var minFloatField));
// Max field
propertySheet.Add(floatPropertyDrawer.CreateGUI(
newValue =>
{
if (newValue < vector1ShaderProperty.rangeValues.x)
propertySheet.warningContainer.Q<Label>().text = "Max cannot be lesser than Min.";
this._preChangeValueCallback("Change Range Property Maximum");
vector1ShaderProperty.rangeValues = new Vector2(vector1ShaderProperty.rangeValues.x, newValue);
this._postChangeValueCallback();
},
vector1ShaderProperty.rangeValues.y,
"Max",
out var maxFloatField));
var defaultField = (FloatField)propertyFloatField;
var minField = (FloatField)minFloatField;
var maxField = (FloatField)maxFloatField;
defaultField.RegisterValueChangedCallback(v =>
{
if (vector1ShaderProperty.sliderType == SliderType.Integer)
defaultField.value = MathF.Round((float)v.newValue);
});
minField.RegisterValueChangedCallback(v =>
{
if (vector1ShaderProperty.sliderType == SliderType.Integer)
minField.value = MathF.Round((float)v.newValue);
});
maxField.RegisterValueChangedCallback(v =>
{
if (vector1ShaderProperty.sliderType == SliderType.Integer)
maxField.value = MathF.Round((float)v.newValue);
});
minField.Q("unity-text-input").RegisterCallback<FocusOutEvent>(evt =>
{
propertySheet.warningContainer.Q<Label>().text = "";
vector1ShaderProperty.value = Mathf.Max(Mathf.Min(vector1ShaderProperty.value, vector1ShaderProperty.rangeValues.y), vector1ShaderProperty.rangeValues.x);
defaultField.value = vector1ShaderProperty.value;
_postChangeValueCallback();
}, TrickleDown.TrickleDown);
maxField.Q("unity-text-input").RegisterCallback<FocusOutEvent>(evt =>
{
propertySheet.warningContainer.Q<Label>().text = "";
vector1ShaderProperty.value = Mathf.Max(Mathf.Min(vector1ShaderProperty.value, vector1ShaderProperty.rangeValues.y), vector1ShaderProperty.rangeValues.x);
defaultField.value = vector1ShaderProperty.value;
_postChangeValueCallback();
}, TrickleDown.TrickleDown);
if (vector1ShaderProperty.sliderType == SliderType.Power)
{
propertySheet.Add(floatPropertyDrawer.CreateGUI(
newValue =>
{
_preChangeValueCallback("Change Slider Power Value");
vector1ShaderProperty.sliderPower = newValue;
_postChangeValueCallback();
},
vector1ShaderProperty.sliderPower,
"Power",
out var propertySliderPowerField));
propertySliderPowerField.tooltip = "The Power factor applied to the Slider";
}
break;
case FloatType.Integer:
var integerPropertyDrawer = new IntegerPropertyDrawer();
// Default field
propertySheet.Add(integerPropertyDrawer.CreateGUI(
newValue =>
{
this._preChangeValueCallback("Change property value");
this._changeValueCallback((float)newValue);
this._postChangeValueCallback();
},
(int)vector1ShaderProperty.value,
isCurrentPropertyGlobal ? "Preview Value" : "Default Value",
out var integerPropertyField));
break;
case FloatType.Enum:
var enumValuePropertyDrawer = new IntegerPropertyDrawer();
// Default field
propertySheet.Add(enumValuePropertyDrawer.CreateGUI(
newValue =>
{
this._preChangeValueCallback("Change property value");
this._changeValueCallback((float)newValue);
this._postChangeValueCallback();
},
(int)vector1ShaderProperty.value,
isCurrentPropertyGlobal ? "Preview Value" : "Default Value",
out var enumValuePropertyField));
var enumPropertyDrawer = new EnumPropertyDrawer();
propertySheet.Add(enumPropertyDrawer.CreateGUI(
newValue =>
{
this._preChangeValueCallback("Change Enum Type");
vector1ShaderProperty.enumType = (EnumType)newValue;
this._postChangeValueCallback(true);
},
(EnumTypeForUI)vector1ShaderProperty.enumType,
"Enum Type",
EnumTypeForUI.ExplicitValues,
out var modePropertyEnumField,
tooltip: "Set the Enum value type."));
switch (vector1ShaderProperty.enumType)
{
case EnumType.Enum:
var container = new IMGUIContainer(() => OnEnumGUIHandler()) { name = "ListContainer" };
AddPropertyRowToSheet(propertySheet, container, "Entries");
break;
case EnumType.CSharpEnum:
var textPropertyDrawer = new TextPropertyDrawer();
propertySheet.Add(textPropertyDrawer.CreateGUI(
newValue =>
{
this._preChangeValueCallback("Change C# Enum Type");
newValue = GetSanitizedEnumRefName(newValue);
vector1ShaderProperty.cSharpEnumString = newValue;
this._postChangeValueCallback(true);
},
vector1ShaderProperty.cSharpEnumString,
"C# Enum Type",
tooltip: "Enter an Enum type."));
break;
case EnumType.KeywordEnum:
default:
break;
}
break;
default:
var defaultFloatPropertyDrawer = new FloatPropertyDrawer();
// Default field
propertySheet.Add(defaultFloatPropertyDrawer.CreateGUI(
newValue =>
{
this._preChangeValueCallback("Change property value");
this._changeValueCallback(newValue);
this._postChangeValueCallback();
},
vector1ShaderProperty.value,
isCurrentPropertyGlobal ? "Preview Value" : "Default Value",
out var defaultFloatPropertyField));
if (isSubGraph)
{
var boolPropertyDrawer = new BoolPropertyDrawer();
propertySheet.Add(boolPropertyDrawer.CreateGUI(
newValue =>
{
_preChangeValueCallback("Change Const Int Mode");
vector1ShaderProperty.LiteralFloatMode = newValue;
// see the node in PropertyNode about a potentially better way to handle this
this._precisionChangedCallback();
_postChangeValueCallback();
},
vector1ShaderProperty.LiteralFloatMode,
"Requires Literal Input",
out _));
}
break;
}
}
void HandleVector2ShaderProperty(PropertySheet propertySheet, Vector2ShaderProperty vector2ShaderProperty)
{
var vector2PropertyDrawer = new Vector2PropertyDrawer();
vector2PropertyDrawer.preValueChangeCallback = () => this._preChangeValueCallback("Change property value");
vector2PropertyDrawer.postValueChangeCallback = () => this._postChangeValueCallback();
propertySheet.Add(vector2PropertyDrawer.CreateGUI(
newValue => _changeValueCallback(newValue),
vector2ShaderProperty.value,
isCurrentPropertyGlobal ? "Preview Value" : "Default Value",
out var propertyVec2Field));
}
void HandleVector3ShaderProperty(PropertySheet propertySheet, Vector3ShaderProperty vector3ShaderProperty)
{
var vector3PropertyDrawer = new Vector3PropertyDrawer();
vector3PropertyDrawer.preValueChangeCallback = () => this._preChangeValueCallback("Change property value");
vector3PropertyDrawer.postValueChangeCallback = () => this._postChangeValueCallback();
propertySheet.Add(vector3PropertyDrawer.CreateGUI(
newValue => _changeValueCallback(newValue),
vector3ShaderProperty.value,
isCurrentPropertyGlobal ? "Preview Value" : "Default Value",
out var propertyVec3Field));
}
void HandleVector4ShaderProperty(PropertySheet propertySheet, Vector4ShaderProperty vector4Property)
{
var vector4PropertyDrawer = new Vector4PropertyDrawer();
vector4PropertyDrawer.preValueChangeCallback = () => this._preChangeValueCallback("Change property value");
vector4PropertyDrawer.postValueChangeCallback = () => this._postChangeValueCallback();
propertySheet.Add(vector4PropertyDrawer.CreateGUI(
newValue => _changeValueCallback(newValue),
vector4Property.value,
isCurrentPropertyGlobal ? "Preview Value" : "Default Value",
out var propertyVec4Field));
}
void HandleColorProperty(PropertySheet propertySheet, ColorShaderProperty colorProperty)
{
var colorPropertyDrawer = new ColorPropertyDrawer();
if (!isSubGraph || shaderInput.promoteToFinalShader)
{
if (colorProperty.isMainColor)
{
var mainColorLabel = new IMGUIContainer(() =>
{
EditorGUI.indentLevel++;
EditorGUILayout.LabelField("Main Color", EditorStyles.largeLabel);
EditorGUILayout.Space();
EditorGUI.indentLevel--;
});
propertySheet.Insert(2, mainColorLabel);
}
}
if (/*shaderInput.isExposed && */(!isSubGraph || shaderInput.promoteToFinalShader) && !isCurrentPropertyGlobal)
{
var enumPropertyDrawer = new EnumPropertyDrawer();
propertySheet.Add(enumPropertyDrawer.CreateGUI(
newValue =>
{
this._preChangeValueCallback("Change Color Mode");
colorProperty.colorMode = (ColorMode)newValue;
this._postChangeValueCallback(true, ModificationScope.Graph);
},
colorProperty.colorMode,
"Mode",
ColorMode.Default,
out var colorModeField));
}
propertySheet.Add(colorPropertyDrawer.CreateGUI(
newValue =>
{
this._preChangeValueCallback("Change property value");
this._changeValueCallback(newValue);
this._postChangeValueCallback();
},
colorProperty.value,
isCurrentPropertyGlobal ? "Preview Value" : "Default Value",
out var propertyColorField));
var colorField = (ColorField)propertyColorField;
colorField.hdr = colorProperty.colorMode == ColorMode.HDR;
}
void HandleTexture2DProperty(PropertySheet propertySheet, Texture2DShaderProperty texture2DProperty)
{
var texture2DPropertyDrawer = new Texture2DPropertyDrawer();
if (!isSubGraph || shaderInput.promoteToFinalShader)
{
if (texture2DProperty.isMainTexture)
{
var mainTextureLabel = new IMGUIContainer(() =>
{
EditorGUI.indentLevel++;
EditorGUILayout.LabelField("Main Texture", EditorStyles.largeLabel);
EditorGUILayout.Space();
EditorGUI.indentLevel--;
});
propertySheet.Insert(2, mainTextureLabel);
}
}
propertySheet.Add(texture2DPropertyDrawer.CreateGUI(
newValue =>
{
this._preChangeValueCallback("Change property value");
this._changeValueCallback(newValue);
this._postChangeValueCallback();
},
texture2DProperty.value.texture,
isCurrentPropertyGlobal ? "Preview Value" : "Default Value",
out var texture2DField
));
if ((!isSubGraph || shaderInput.promoteToFinalShader) && !isCurrentPropertyGlobal)
{
var enumPropertyDrawer = new EnumPropertyDrawer();
propertySheet.Add(enumPropertyDrawer.CreateGUI(
newValue =>
{
this._preChangeValueCallback("Change Texture mode");
if (texture2DProperty.defaultType == (Texture2DShaderProperty.DefaultType)newValue)
return;
texture2DProperty.defaultType = (Texture2DShaderProperty.DefaultType)newValue;
this._postChangeValueCallback(false, ModificationScope.Graph);
},
texture2DProperty.defaultType,
"Mode",
Texture2DShaderProperty.DefaultType.White,
out var textureModeField,
tooltip: "Fallback texture if none is provided."));
textureModeField.SetEnabled(texture2DProperty.generatePropertyBlock);
}
if (!isSubGraph || shaderInput.promoteToFinalShader)
{
var togglePropertyDrawer = new ToggleDataPropertyDrawer();
propertySheet.Add(togglePropertyDrawer.CreateGUI(
newValue =>
{
this._preChangeValueCallback("Change Use Tilling and Offset");
if (texture2DProperty.useTilingAndOffset == newValue.isOn)
return;
texture2DProperty.useTilingAndOffset = newValue.isOn;
this._postChangeValueCallback();
},
new ToggleData(texture2DProperty.useTilingAndOffset, true),
"Use Tiling and Offset",
out var tilingAndOffsetToggle));
propertySheet.Add(togglePropertyDrawer.CreateGUI(
newValue =>
{
this._preChangeValueCallback("Change Use TexelSize");
if (texture2DProperty.useTexelSize == newValue.isOn)
return;
texture2DProperty.useTexelSize = newValue.isOn;
this._postChangeValueCallback();
},
new ToggleData(texture2DProperty.useTexelSize, true),
"Use TexelSize",
out var texelSizeToggle));
}
}
void HandleTexture2DArrayProperty(PropertySheet propertySheet, Texture2DArrayShaderProperty texture2DArrayProperty)
{
var texture2DArrayPropertyDrawer = new Texture2DArrayPropertyDrawer();
propertySheet.Add(texture2DArrayPropertyDrawer.CreateGUI(
newValue =>
{
this._preChangeValueCallback("Change property value");
this._changeValueCallback(newValue);
this._postChangeValueCallback();
},
texture2DArrayProperty.value.textureArray,
isCurrentPropertyGlobal ? "Preview Value" : "Default Value",
out var texture2DArrayField
));
}
#region VT reorderable list handler
void HandleVirtualTextureProperty(PropertySheet propertySheet, VirtualTextureShaderProperty virtualTextureProperty)
{
var container = new IMGUIContainer(() => OnVTGUIHandler(virtualTextureProperty)) { name = "ListContainer" };
AddPropertyRowToSheet(propertySheet, container, "Layers");
m_VTLayer_Name = new TextField();
m_VTLayer_Name.isDelayed = true;
m_VTLayer_Name.RegisterValueChangedCallback(
evt =>
{
int index = m_VTReorderableList.index;
if (index >= 0 && index < m_VTReorderableList.list.Count)
{
var svt = m_VTReorderableList.list[index] as SerializableVirtualTextureLayer;
var otherPropertyNames = graphData.BuildPropertyDisplayNameList(virtualTextureProperty, svt.layerName);
var newLayerName = GraphUtil.SanitizeName(otherPropertyNames, "{0} ({1})", evt.newValue);
if (newLayerName != svt.layerName)
{
this._preChangeValueCallback("Change Layer Name");
svt.layerName = newLayerName;
this._postChangeValueCallback(false, ModificationScope.Graph);
m_VTLayer_Name.SetValueWithoutNotify(newLayerName);
}
}
});
AddPropertyRowToSheet(propertySheet, m_VTLayer_Name, " Layer Name");
m_VTLayer_RefName = new IdentifierField();
m_VTLayer_RefName.isDelayed = true;
m_VTLayer_RefName.RegisterValueChangedCallback(
evt =>
{
int index = m_VTReorderableList.index;
if (index >= 0 && index < m_VTReorderableList.list.Count)
{
var svt = m_VTReorderableList.list[index] as SerializableVirtualTextureLayer;
var otherPropertyRefNames = graphData.BuildPropertyReferenceNameList(virtualTextureProperty, svt.layerRefName);
var newName = NodeUtils.ConvertToValidHLSLIdentifier(evt.newValue);
var newLayerRefName = GraphUtil.SanitizeName(otherPropertyRefNames, "{0}_{1}", newName);
if (newLayerRefName != svt.layerRefName)
{
this._preChangeValueCallback("Change Layer Ref Name");
svt.layerRefName = newLayerRefName;
this._postChangeValueCallback(false, ModificationScope.Graph);
}
// Always update the display name to the sanitized name. If an invalid name was entered that ended up being sanitized to the old value,
// the text box still needs to be updated to display the sanitized name.
m_VTLayer_RefName.SetValueWithoutNotify(newLayerRefName);
}
});
AddPropertyRowToSheet(propertySheet, m_VTLayer_RefName, " Layer Reference");
m_VTLayer_Texture = new ObjectField();
m_VTLayer_Texture.objectType = typeof(Texture);
m_VTLayer_Texture.allowSceneObjects = false;
m_VTLayer_Texture.RegisterValueChangedCallback(
evt =>
{
this._preChangeValueCallback("Change Layer Texture");
int index = m_VTReorderableList.index;
if (index >= 0 && index < m_VTReorderableList.list.Count)
(m_VTReorderableList.list[index] as SerializableVirtualTextureLayer).layerTexture.texture = (evt.newValue as Texture);
this._postChangeValueCallback(false, ModificationScope.Graph);
});
AddPropertyRowToSheet(propertySheet, m_VTLayer_Texture, " Layer Texture");
m_VTLayer_TextureType = new EnumField();
m_VTLayer_TextureType.Init(LayerTextureType.Default);
m_VTLayer_TextureType.RegisterValueChangedCallback(
evt =>
{
this._preChangeValueCallback("Change Layer Texture Type");
int index = m_VTReorderableList.index;
if (index >= 0 && index < m_VTReorderableList.list.Count)
(m_VTReorderableList.list[index] as SerializableVirtualTextureLayer).layerTextureType = (LayerTextureType)evt.newValue;
this._postChangeValueCallback(false, ModificationScope.Graph);
});
AddPropertyRowToSheet(propertySheet, m_VTLayer_TextureType, " Layer Texture Type");
}
private void OnVTGUIHandler(VirtualTextureShaderProperty property)
{
if (m_VTReorderableList == null)
{
VTRecreateList(property);
VTAddCallbacks(property);
// update selected entry to reflect default selection
VTSelectEntry(m_VTReorderableList);
}
m_VTReorderableList.index = m_VTSelectedIndex;
m_VTReorderableList.DoLayoutList();
}
internal void VTRecreateList(VirtualTextureShaderProperty property)
{
// Create reorderable list from entries
m_VTReorderableList = new ReorderableList(property.value.layers, typeof(SerializableVirtualTextureLayer), true, true, true, true);
}
private void VTAddCallbacks(VirtualTextureShaderProperty property)
{
// Draw Header
m_VTReorderableList.drawHeaderCallback = (Rect rect) =>
{
int indent = 14;
var displayRect = new Rect(rect.x + indent, rect.y, rect.width, rect.height);
EditorGUI.LabelField(displayRect, "Layer Name");
};
// Draw Element
m_VTReorderableList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
{
SerializableVirtualTextureLayer entry = ((SerializableVirtualTextureLayer)m_VTReorderableList.list[index]);
EditorGUI.BeginChangeCheck();
EditorGUI.LabelField(rect, entry.layerName);
};
// Element height
m_VTReorderableList.elementHeightCallback = (int indexer) =>
{
return m_VTReorderableList.elementHeight;
};
// Can add
m_VTReorderableList.onCanAddCallback = (ReorderableList list) =>
{
return list.count < 4;
};
// Can remove
m_VTReorderableList.onCanRemoveCallback = (ReorderableList list) =>
{
return list.count > 1;
};
void AddEntryLamda(ReorderableList list) => VTAddEntry(list, property);
void RemoveEntryLamda(ReorderableList list) => VTRemoveEntry(list, property);
// Add callback delegates
m_VTReorderableList.onSelectCallback += VTSelectEntry;
m_VTReorderableList.onAddCallback += AddEntryLamda;
m_VTReorderableList.onRemoveCallback += RemoveEntryLamda;
m_VTReorderableList.onReorderCallback += VTReorderEntries;
}
private void VTSelectEntry(ReorderableList list)
{
m_VTSelectedIndex = list.index;
if (m_VTSelectedIndex >= 0 && m_VTSelectedIndex < list.count)
{
m_VTLayer_Name.SetEnabled(true);
m_VTLayer_RefName.SetEnabled(true);
m_VTLayer_Texture.SetEnabled(true);
m_VTLayer_TextureType.SetEnabled(true);
m_VTLayer_Name.SetValueWithoutNotify((list.list[m_VTSelectedIndex] as SerializableVirtualTextureLayer).layerName);
m_VTLayer_RefName.SetValueWithoutNotify((list.list[m_VTSelectedIndex] as SerializableVirtualTextureLayer).layerRefName);
m_VTLayer_Texture.SetValueWithoutNotify((list.list[m_VTSelectedIndex] as SerializableVirtualTextureLayer).layerTexture.texture);
m_VTLayer_TextureType.SetValueWithoutNotify((list.list[m_VTSelectedIndex] as SerializableVirtualTextureLayer).layerTextureType);
}
else
{
m_VTLayer_Name.SetEnabled(false);
m_VTLayer_RefName.SetEnabled(false);
m_VTLayer_Texture.SetEnabled(false);
m_VTLayer_TextureType.SetEnabled(false);
m_VTLayer_Name.SetValueWithoutNotify("");
m_VTLayer_RefName.SetValueWithoutNotify("");
m_VTLayer_Texture.SetValueWithoutNotify(null);
m_VTLayer_TextureType.SetValueWithoutNotify(LayerTextureType.Default);
}
}
private void VTAddEntry(ReorderableList list, VirtualTextureShaderProperty property)
{
this._preChangeValueCallback("Add Virtual Texture Entry");
int index = VTGetFirstUnusedID(property);
if (index <= 0)
return; // Error has already occured, don't attempt to add this entry.
var layerName = "Layer" + index.ToString();
// Add new entry
property.value.layers.Add(new SerializableVirtualTextureLayer(layerName, new SerializableTexture()));
// Update Blackboard & Nodes
//DirtyNodes();
this._postChangeValueCallback(true);
m_VTSelectedIndex = list.list.Count - 1;
//Hack to handle downstream SampleVirtualTextureNodes
graphData.ValidateGraph();
}
// Allowed indicies are 1-MAX_ENUM_ENTRIES
private int VTGetFirstUnusedID(VirtualTextureShaderProperty property)
{
List<int> ususedIDs = new List<int>();
foreach (SerializableVirtualTextureLayer virtualTextureEntry in property.value.layers)
{
ususedIDs.Add(property.value.layers.IndexOf(virtualTextureEntry));
}
for (int x = 1; x <= 4; x++)
{
if (!ususedIDs.Contains(x))
return x;
}
Debug.LogError("GetFirstUnusedID: Attempting to get unused ID when all IDs are used.");
return -1;
}
private void VTRemoveEntry(ReorderableList list, VirtualTextureShaderProperty property)
{
this._preChangeValueCallback("Remove Virtual Texture Entry");
// Remove entry
m_VTSelectedIndex = list.index;
var selectedEntry = (SerializableVirtualTextureLayer)m_VTReorderableList.list[list.index];
property.value.layers.Remove(selectedEntry);
// Update Blackboard & Nodes
//DirtyNodes();
this._postChangeValueCallback(true);
m_VTSelectedIndex = m_VTSelectedIndex >= list.list.Count - 1 ? list.list.Count - 1 : m_VTSelectedIndex;
//Hack to handle downstream SampleVirtualTextureNodes
graphData.ValidateGraph();
}
private void VTReorderEntries(ReorderableList list)
{
this._postChangeValueCallback(true);
}
#endregion
void HandleTexture3DProperty(PropertySheet propertySheet, Texture3DShaderProperty texture3DShaderProperty)
{
var texture3DPropertyDrawer = new Texture3DPropertyDrawer();
propertySheet.Add(texture3DPropertyDrawer.CreateGUI(
newValue =>
{
this._preChangeValueCallback("Change property value");
this._changeValueCallback(newValue);
this._postChangeValueCallback();
},
texture3DShaderProperty.value.texture,
isCurrentPropertyGlobal ? "Preview Value" : "Default Value",
out var texture3DField
));
}
void HandleCubemapProperty(PropertySheet propertySheet, CubemapShaderProperty cubemapProperty)
{
var cubemapPropertyDrawer = new CubemapPropertyDrawer();
propertySheet.Add(cubemapPropertyDrawer.CreateGUI(
newValue =>
{
this._preChangeValueCallback("Change property value");
this._changeValueCallback(newValue);
this._postChangeValueCallback();
},
cubemapProperty.value.cubemap,
isCurrentPropertyGlobal ? "Preview Value" : "Default Value",
out var propertyCubemapField
));
}
void HandleBooleanProperty(PropertySheet propertySheet, BooleanShaderProperty booleanProperty)
{
var toggleDataPropertyDrawer = new ToggleDataPropertyDrawer();
propertySheet.Add(toggleDataPropertyDrawer.CreateGUI(
newValue =>
{
this._preChangeValueCallback("Change property value");
this._changeValueCallback(newValue);
this._postChangeValueCallback();
},
new ToggleData(booleanProperty.value),
isCurrentPropertyGlobal ? "Preview Value" : "Default Value",
out var propertyToggle));
}
void HandleMatrix2PropertyField(PropertySheet propertySheet, Matrix2ShaderProperty matrix2Property)
{
var matrixPropertyDrawer = new MatrixPropertyDrawer
{
dimension = MatrixPropertyDrawer.MatrixDimensions.Two,
PreValueChangeCallback = () => this._preChangeValueCallback("Change property value"),
PostValueChangeCallback = () => this._postChangeValueCallback(),
MatrixRowFetchCallback = (rowNumber) => matrix2Property.value.GetRow(rowNumber)
};
propertySheet.Add(matrixPropertyDrawer.CreateGUI(
newValue => { this._changeValueCallback(newValue); },
matrix2Property.value,
"Preview Value",
out var propertyMatrixField));
}
void HandleMatrix3PropertyField(PropertySheet propertySheet, Matrix3ShaderProperty matrix3Property)
{
var matrixPropertyDrawer = new MatrixPropertyDrawer
{
dimension = MatrixPropertyDrawer.MatrixDimensions.Three,
PreValueChangeCallback = () => this._preChangeValueCallback("Change property value"),
PostValueChangeCallback = () => this._postChangeValueCallback(),
MatrixRowFetchCallback = (rowNumber) => matrix3Property.value.GetRow(rowNumber)
};
propertySheet.Add(matrixPropertyDrawer.CreateGUI(
newValue => { this._changeValueCallback(newValue); },
matrix3Property.value,
"Preview Value",
out var propertyMatrixField));
}
void HandleMatrix4PropertyField(PropertySheet propertySheet, Matrix4ShaderProperty matrix4Property)
{
var matrixPropertyDrawer = new MatrixPropertyDrawer
{
dimension = MatrixPropertyDrawer.MatrixDimensions.Four,
PreValueChangeCallback = () => this._preChangeValueCallback("Change property value"),
PostValueChangeCallback = () => this._postChangeValueCallback(),
MatrixRowFetchCallback = (rowNumber) => matrix4Property.value.GetRow(rowNumber)
};
propertySheet.Add(matrixPropertyDrawer.CreateGUI(
newValue => { this._changeValueCallback(newValue); },
matrix4Property.value,
"Preview Value",
out var propertyMatrixField));
}
void HandleSamplerStatePropertyField(PropertySheet propertySheet, SamplerStateShaderProperty samplerStateShaderProperty)
{
var enumPropertyDrawer = new EnumPropertyDrawer();
propertySheet.Add(enumPropertyDrawer.CreateGUI(
newValue =>
{
this._preChangeValueCallback("Change property value");
TextureSamplerState state = samplerStateShaderProperty.value;
state.filter = (TextureSamplerState.FilterMode)newValue;
samplerStateShaderProperty.value = state;
this._postChangeValueCallback(false, ModificationScope.Graph);
this.inspectorUpdateDelegate();
},
samplerStateShaderProperty.value.filter,
"Filter",
TextureSamplerState.FilterMode.Linear,
out var filterVisualElement));
propertySheet.Add(enumPropertyDrawer.CreateGUI(
newValue =>
{
this._preChangeValueCallback("Change property value");
TextureSamplerState state = samplerStateShaderProperty.value;
state.wrap = (TextureSamplerState.WrapMode)newValue;
samplerStateShaderProperty.value = state;
this._postChangeValueCallback(false, ModificationScope.Graph);
this.inspectorUpdateDelegate();
},
samplerStateShaderProperty.value.wrap,
"Wrap",
TextureSamplerState.WrapMode.Repeat,
out var wrapVisualElement));
propertySheet.Add(enumPropertyDrawer.CreateGUI(
newValue =>
{
this._preChangeValueCallback("Change property value");
TextureSamplerState state = samplerStateShaderProperty.value;
state.anisotropic = (TextureSamplerState.Anisotropic)newValue;
samplerStateShaderProperty.value = state;
this._postChangeValueCallback(false, ModificationScope.Graph);
this.inspectorUpdateDelegate();
},
samplerStateShaderProperty.value.anisotropic,
"Aniso",
TextureSamplerState.Anisotropic.None,
out var anisoVisualElement));
}
void HandleGradientPropertyField(PropertySheet propertySheet, GradientShaderProperty gradientShaderProperty)
{
var gradientPropertyDrawer = new GradientPropertyDrawer();
propertySheet.Add(gradientPropertyDrawer.CreateGUI(
newValue =>
{
this._preChangeValueCallback("Change property value");
this._changeValueCallback(newValue);
this._postChangeValueCallback();
},
gradientShaderProperty.value,
"Default Value",
out var propertyGradientField));
}
void BuildAttributesFields(PropertySheet propertySheet)
{
if ((isSubGraph || isCurrentPropertyGlobal) && !shaderInput.promoteToFinalShader)
return;
if (shaderInput.isExposed && shaderInput is AbstractShaderProperty property)
{
var container = new IMGUIContainer(() => OnCustomAttributesGUIHandler()) { name = "ListContainer" };
AddPropertyRowToSheet(propertySheet, container, "Custom Attributes", "Add Custom Attributes to be used with Custom Property Drawers.");
}
}
ReorderableList m_CustomAttributesReorderableList;
int m_CustomAttributesSelectedIndex;
void OnCustomAttributesGUIHandler()
{
if (m_CustomAttributesReorderableList == null)
{
CustomAttributesRecreateList();
CustomAttributesAddCallbacks();
}
m_CustomAttributesReorderableList.index = m_CustomAttributesSelectedIndex;
m_CustomAttributesReorderableList.DoLayoutList();
}
internal void CustomAttributesRecreateList()
{
if (shaderInput is AbstractShaderProperty property)
{
// Create reorderable list from entries
m_CustomAttributesReorderableList = new ReorderableList(property.customAttributes, typeof(Tuple<string, int>), true, true, true, true);
}
}
void CustomAttributesAddCallbacks()
{
if (shaderInput is AbstractShaderProperty property)
{
// Draw Header
m_CustomAttributesReorderableList.drawHeaderCallback = (Rect rect) =>
{
int indent = 14;
var displayRect = new Rect(rect.x + indent, rect.y, (rect.width - indent) / 2, rect.height);
EditorGUI.LabelField(displayRect, "Name");
var referenceRect = new Rect((rect.x + indent) + (rect.width - indent) / 2, rect.y, (rect.width - indent) / 2, rect.height);
EditorGUI.LabelField(referenceRect, "Value");
};
// Draw Element
m_CustomAttributesReorderableList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
{
var entry = (AbstractShaderProperty.PropertyAttribute)m_CustomAttributesReorderableList.list[index];
Rect displayRect = new Rect(rect.x, rect.y, rect.width / 2, EditorGUIUtility.singleLineHeight);
EditorGUI.BeginChangeCheck();
var name = EditorGUI.DelayedTextField(displayRect, entry.name, EditorStyles.label);
var value = EditorGUI.DelayedTextField(new Rect(rect.x + rect.width / 2, rect.y, rect.width / 2, EditorGUIUtility.singleLineHeight), entry.value);
if (EditorGUI.EndChangeCheck())
{
this._preChangeValueCallback("Edit Custom Attribute Entry");
name = GetSanitizedReferenceName(name);
if (string.IsNullOrWhiteSpace(name))
Debug.LogWarning("Invalid Attribute name. Attribute names cannot be empty or all whitespace.");
else if (int.TryParse(name, out int intVal) || float.TryParse(name, out float floatVal))
Debug.LogWarning("Invalid Attribute name. Attribute names cannot be valid integer or floating point numbers.");
value = GetSanitizedAttributeValue(value);
property.customAttributes[index].name = name;
property.customAttributes[index].value = value;
this._postChangeValueCallback(true);
}
};
// Add
m_CustomAttributesReorderableList.onAddCallback = (ReorderableList list) =>
{
this._preChangeValueCallback("Add Custom Attribute");
// Add new entry
property.customAttributes.Add(new("MyCustomAttribute", string.Empty));
// Update GUI
this._postChangeValueCallback(true);
m_CustomAttributesSelectedIndex = list.list.Count - 1;
};
// Remove
m_CustomAttributesReorderableList.onRemoveCallback = (ReorderableList list) =>
{
this._preChangeValueCallback("Remove Custom Attribute");
// Remove entry
property.customAttributes.RemoveAt(list.index);
// Rebuild();
this._postChangeValueCallback(true);
m_CustomAttributesSelectedIndex = m_CustomAttributesSelectedIndex >= list.list.Count - 1 ? list.list.Count - 1 : m_CustomAttributesSelectedIndex;
};
// Element height
m_CustomAttributesReorderableList.elementHeightCallback = (int indexer) =>
{
return m_CustomAttributesReorderableList.elementHeight;
};
// Can add
m_CustomAttributesReorderableList.onCanAddCallback = (ReorderableList list) =>
{
return list.count < 8;
};
// Can remove
m_CustomAttributesReorderableList.onCanRemoveCallback = (ReorderableList list) => true;
// Select
m_CustomAttributesReorderableList.onSelectCallback = (ReorderableList list) =>
{
m_CustomAttributesSelectedIndex = list.index;
};
// Reorder
m_CustomAttributesReorderableList.onReorderCallbackWithDetails = (ReorderableList list, int oldIndex, int newIndex) =>
{
this._preChangeValueCallback("Reordered Custom Attributes");
this._postChangeValueCallback(true);
};
}
}
enum KeywordShaderStageDropdownUI // maps to KeywordShaderStage, this enum ONLY used for the UI dropdown menu
{
All = KeywordShaderStage.All,
Vertex = KeywordShaderStage.Vertex,
Fragment = KeywordShaderStage.Fragment,
}
void BuildKeywordFields(PropertySheet propertySheet, ShaderInput shaderInput)
{
var keyword = shaderInput as ShaderKeyword;
if (keyword == null)
return;
var enumPropertyDrawer = new EnumPropertyDrawer();
propertySheet.Add(enumPropertyDrawer.CreateGUI(
newValue =>
{
this._preChangeValueCallback("Change Keyword type");
if (keyword.keywordDefinition == (KeywordDefinition)newValue)
return;
keyword.keywordDefinition = (KeywordDefinition)newValue;
UpdateEnableState();
this._postChangeValueCallback(true, ModificationScope.Nothing);
},
keyword.keywordDefinition,
"Definition",
KeywordDefinition.ShaderFeature,
out var typeField,
tooltip: "Indicate how the keyword is defined and under what circumstances its permutations will be compiled."));
if (keyword.keywordDefinition == KeywordDefinition.ShaderFeature && isSubGraph && !keyword.promoteToFinalShader)
{
var help = new HelpBoxRow("Shader Feature Keywords in SubGraphs do not generate variant permutations.", MessageType.Info);
propertySheet.Add(help);
}
typeField.SetEnabled(!keyword.isBuiltIn);
{
var isOverridablePropertyDrawer = new ToggleDataPropertyDrawer();
bool enabledState = keyword.keywordDefinition != KeywordDefinition.Predefined;
bool toggleState = keyword.keywordScope == KeywordScope.Global || !enabledState;
propertySheet.Add(isOverridablePropertyDrawer.CreateGUI(
newValue =>
{
this._preChangeValueCallback("Change Keyword Is Overridable");
keyword.keywordScope = newValue.isOn
? KeywordScope.Global
: KeywordScope.Local;
},
new ToggleData(toggleState),
"Is Overridable",
out keywordScopeField,
tooltip: "Indicate whether this keyword's state can be overridden through the Shader.SetKeyword scripting interface."));
keywordScopeField.SetEnabled(enabledState);
}
BuildExposedField(propertySheet);
{
propertySheet.Add(enumPropertyDrawer.CreateGUI(
newValue =>
{
this._preChangeValueCallback("Change Keyword stage");
if (keyword.keywordStages == (KeywordShaderStage)newValue)
return;
keyword.keywordStages = (KeywordShaderStage)newValue;
},
(KeywordShaderStageDropdownUI)keyword.keywordStages,
"Stages",
KeywordShaderStageDropdownUI.All,
out keywordScopeField,
tooltip: "Indicates which shader stages this keyword is relevant for."));
}
switch (keyword.keywordType)
{
case KeywordType.Boolean:
BuildBooleanKeywordField(propertySheet, keyword);
break;
case KeywordType.Enum:
BuildEnumKeywordField(propertySheet, keyword);
break;
}
}
void BuildBooleanKeywordField(PropertySheet propertySheet, ShaderKeyword keyword)
{
var toggleDataPropertyDrawer = new ToggleDataPropertyDrawer();
propertySheet.Add(toggleDataPropertyDrawer.CreateGUI(
newValue =>
{
this._preChangeValueCallback("Change property value");
keyword.value = newValue.isOn ? 1 : 0;
if (graphData.owner.materialArtifact)
{
graphData.owner.materialArtifact.SetFloat(keyword.referenceName, keyword.value);
MaterialEditor.ApplyMaterialPropertyDrawers(graphData.owner.materialArtifact);
}
this._postChangeValueCallback(false, ModificationScope.Graph);
},
new ToggleData(keyword.value == 1),
keyword.keywordDefinition == KeywordDefinition.Predefined ? "Preview Value" : "Default Value",
out var boolKeywordField));
}
void BuildEnumKeywordField(PropertySheet propertySheet, ShaderKeyword keyword)
{
// Clamp value between entry list
int value = Mathf.Clamp(keyword.value, 0, keyword.entries.Count - 1);
// Default field
var field = new PopupField<string>(keyword.entries.Select(x => x.displayName).ToList(), value);
field.RegisterValueChangedCallback(evt =>
{
this._preChangeValueCallback("Change Keyword Value");
keyword.value = field.index;
if (graphData.owner.materialArtifact)
{
graphData.owner.materialArtifact.SetFloat(keyword.referenceName, field.index);
MaterialEditor.ApplyMaterialPropertyDrawers(graphData.owner.materialArtifact);
}
this._postChangeValueCallback(false, ModificationScope.Graph);
});
AddPropertyRowToSheet(propertySheet, field, keyword.keywordDefinition == KeywordDefinition.Predefined ? "Preview Value" : "Default Value");
var container = new IMGUIContainer(() => OnKeywordGUIHandler()) { name = "ListContainer" };
AddPropertyRowToSheet(propertySheet, container, "Entries");
container.SetEnabled(!keyword.isBuiltIn);
}
static void AddPropertyRowToSheet(PropertySheet propertySheet, VisualElement control, string labelName, string tooltip = default)
{
propertySheet.Add(new PropertyRow(new Label(labelName)), (row) =>
{
row.styleSheets.Add(Resources.Load<StyleSheet>("Styles/PropertyRow"));
row.Add(control);
row.tooltip = tooltip;
});
}
ReorderableList m_EnumReorderableList;
int m_EnumSelectedIndex;
void OnEnumGUIHandler()
{
if (m_EnumReorderableList == null)
{
EnumRecreateList();
EnumAddCallbacks();
}
m_EnumReorderableList.index = m_EnumSelectedIndex;
m_EnumReorderableList.DoLayoutList();
}
void CheckEnumNamesAndValuesListLength(Vector1ShaderProperty vector1ShaderProperty)
{
var diff = vector1ShaderProperty.enumNames.Count - vector1ShaderProperty.enumValues.Count;
if (diff == 0)
return;
if (diff > 0) // missing values
{
for (int i = 0; i < diff; i++)
vector1ShaderProperty.enumValues.Add(0);
}
else // missing names
{
for (int i = 0; i < -diff; i++)
vector1ShaderProperty.enumNames.Add("_");
}
}
internal void EnumRecreateList()
{
if (shaderInput is Vector1ShaderProperty vector1Property && vector1Property.floatType == FloatType.Enum && vector1Property.enumType == EnumType.Enum)
{
// Create a virtual list of names and values
List<(string name, int value)> list = new();
CheckEnumNamesAndValuesListLength(vector1Property);
for (int i = 0; i < vector1Property.enumNames.Count; i++)
{
var name = vector1Property.enumNames[i];
var value = vector1Property.enumValues[i];
list.Add(new(name, value));
}
// Create reorderable list from entries
m_EnumReorderableList = new ReorderableList(list, typeof(Tuple<string, int>), true, true, true, true);
}
}
void EnumAddCallbacks()
{
if (shaderInput is Vector1ShaderProperty vector1Property && vector1Property.floatType == FloatType.Enum && vector1Property.enumType == EnumType.Enum)
{
// Draw Header
m_EnumReorderableList.drawHeaderCallback = (Rect rect) =>
{
int indent = 14;
var displayRect = new Rect(rect.x + indent, rect.y, (rect.width - indent) / 2, rect.height);
EditorGUI.LabelField(displayRect, "Name");
var referenceRect = new Rect((rect.x + indent) + (rect.width - indent) / 2, rect.y, (rect.width - indent) / 2, rect.height);
EditorGUI.LabelField(referenceRect, "Value");
};
// Draw Element
m_EnumReorderableList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
{
(string name, int value) entry = ((string name, int value))m_EnumReorderableList.list[index];
Rect displayRect = new Rect(rect.x, rect.y, rect.width / 2, EditorGUIUtility.singleLineHeight);
EditorGUI.BeginChangeCheck();
var name = EditorGUI.DelayedTextField(displayRect, entry.name, EditorStyles.label);
var value = EditorGUI.IntField(new Rect(rect.x + rect.width / 2, rect.y, rect.width / 2, EditorGUIUtility.singleLineHeight), entry.value);
if (EditorGUI.EndChangeCheck())
{
this._preChangeValueCallback("Edit Enum Entry");
name = GraphUtil.SanitizeName(vector1Property.enumNames, "{0} {1}", name, m_DisplayNameDisallowedPattern);
if (string.IsNullOrWhiteSpace(name))
Debug.LogWarning("Invalid display name. Display names cannot be empty or all whitespace.");
else if (int.TryParse(name, out int intVal) || float.TryParse(name, out float floatVal))
Debug.LogWarning("Invalid display name. Display names cannot be valid integer or floating point numbers.");
vector1Property.enumNames[index] = name;
vector1Property.enumValues[index] = value;
this._postChangeValueCallback(true);
}
};
// Add
m_EnumReorderableList.onAddCallback = (ReorderableList list) =>
{
this._preChangeValueCallback("Add Enum Entry");
var name = "New";
name = GraphUtil.SanitizeName(vector1Property.enumNames, "{0} {1}", name, m_DisplayNameDisallowedPattern);
var value = FindSmallestMissingPositive(vector1Property.enumValues);
// Add new entry
vector1Property.enumNames.Add(name);
vector1Property.enumValues.Add(value);
// Update GUI
this._postChangeValueCallback(true);
m_EnumSelectedIndex = list.count - 1;
};
// Remove
m_EnumReorderableList.onRemoveCallback = (ReorderableList list) =>
{
this._preChangeValueCallback("Remove Keyword Entry");
// Remove entry
vector1Property.enumNames.RemoveAt(list.index);
vector1Property.enumValues.RemoveAt(list.index);
// Rebuild();
this._postChangeValueCallback(true);
m_EnumSelectedIndex = m_EnumSelectedIndex >= list.list.Count - 1 ? list.list.Count - 1 : m_EnumSelectedIndex;
};
// Element height
m_EnumReorderableList.elementHeightCallback = (int indexer) =>
{
return m_EnumReorderableList.elementHeight;
};
// Can add
m_EnumReorderableList.onCanAddCallback = (ReorderableList list) =>
{
return list.count < 7;
};
// Can remove
m_EnumReorderableList.onCanRemoveCallback = (ReorderableList list) =>
{
return list.count > 1;
};
// Select
m_EnumReorderableList.onSelectCallback = (ReorderableList list) =>
{
m_EnumSelectedIndex = list.index;
};
// Reorder
m_EnumReorderableList.onReorderCallbackWithDetails = (ReorderableList list, int oldIndex, int newIndex) =>
{
this._preChangeValueCallback("Reordered Enum Entries");
var name = vector1Property.enumNames[oldIndex];
var value = vector1Property.enumValues[oldIndex];
vector1Property.enumNames.RemoveAt(oldIndex);
vector1Property.enumValues.RemoveAt(oldIndex);
vector1Property.enumNames.Insert(newIndex, name);
vector1Property.enumValues.Insert(newIndex, value);
this._postChangeValueCallback(true);
};
}
}
static int FindSmallestMissingPositive(List<int> nums)
{
HashSet<int> positiveNumbers = new HashSet<int>(nums.Where(x => x >= 0));
int smallestMissing = 0;
while (positiveNumbers.Contains(smallestMissing))
{
smallestMissing++;
}
return smallestMissing;
}
void OnKeywordGUIHandler()
{
if (m_KeywordReorderableList == null)
{
KeywordRecreateList();
KeywordAddCallbacks();
}
m_KeywordReorderableList.index = m_KeywordSelectedIndex;
m_KeywordReorderableList.DoLayoutList();
}
internal void KeywordRecreateList()
{
if (!(shaderInput is ShaderKeyword keyword))
return;
// Create reorderable list from entries
m_KeywordReorderableList = new ReorderableList(keyword.entries, typeof(KeywordEntry), true, true, true, true);
}
void KeywordAddCallbacks()
{
if (!(shaderInput is ShaderKeyword keyword))
return;
// Draw Header
m_KeywordReorderableList.drawHeaderCallback = (Rect rect) =>
{
int indent = 14;
var displayRect = new Rect(rect.x + indent, rect.y, (rect.width - indent) / 2, rect.height);
EditorGUI.LabelField(displayRect, "Entry Name");
var referenceRect = new Rect((rect.x + indent) + (rect.width - indent) / 2, rect.y, (rect.width - indent) / 2, rect.height);
EditorGUI.LabelField(referenceRect, "Reference Suffix", keyword.isBuiltIn ? EditorStyles.label : greyLabel);
};
// Draw Element
m_KeywordReorderableList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
{
KeywordEntry entry = ((KeywordEntry)m_KeywordReorderableList.list[index]);
EditorGUI.BeginChangeCheck();
Rect displayRect = new Rect(rect.x, rect.y, rect.width / 2, EditorGUIUtility.singleLineHeight);
var displayName = EditorGUI.DelayedTextField(displayRect, entry.displayName, EditorStyles.label);
//This is gross but I cant find any other way to make a DelayedTextField have a tooltip (tried doing the empty label on the field itself and it didnt work either)
EditorGUI.LabelField(displayRect, new GUIContent("", "Enum keyword display names can only use alphanumeric characters, whitespace and `_`"));
var referenceName = EditorGUI.TextField(new Rect(rect.x + rect.width / 2, rect.y, rect.width / 2, EditorGUIUtility.singleLineHeight), entry.referenceName,
keyword.isBuiltIn ? EditorStyles.label : greyLabel);
if (EditorGUI.EndChangeCheck())
{
this._preChangeValueCallback("Edit Enum Keyword Entry");
displayName = GetSanitizedDisplayName(displayName);
referenceName = GetSanitizedReferenceName(displayName.ToUpper());
var duplicateIndex = FindDuplicateKeywordReferenceNameIndex(entry.id, referenceName);
if (duplicateIndex != -1)
{
var duplicateEntry = ((KeywordEntry)m_KeywordReorderableList.list[duplicateIndex]);
Debug.LogWarning($"Display name '{displayName}' will create the same reference name '{referenceName}' as entry {duplicateIndex + 1} with display name '{duplicateEntry.displayName}'.");
}
else if (string.IsNullOrWhiteSpace(displayName))
Debug.LogWarning("Invalid display name. Display names cannot be empty or all whitespace.");
else if (int.TryParse(displayName, out int intVal) || float.TryParse(displayName, out float floatVal))
Debug.LogWarning("Invalid display name. Display names cannot be valid integer or floating point numbers.");
else
keyword.entries[index] = new KeywordEntry(GetFirstUnusedKeywordID(), displayName, referenceName);
this._postChangeValueCallback(true);
}
};
// Element height
m_KeywordReorderableList.elementHeightCallback = (int indexer) =>
{
return m_KeywordReorderableList.elementHeight;
};
// Can add
m_KeywordReorderableList.onCanAddCallback = (ReorderableList list) =>
{
return list.count < 8;
};
// Can remove
m_KeywordReorderableList.onCanRemoveCallback = (ReorderableList list) =>
{
return list.count > 2;
};
// Add callback delegates
m_KeywordReorderableList.onSelectCallback += KeywordSelectEntry;
m_KeywordReorderableList.onAddCallback += KeywordAddEntry;
m_KeywordReorderableList.onRemoveCallback += KeywordRemoveEntry;
m_KeywordReorderableList.onReorderCallback += KeywordReorderEntries;
}
void KeywordSelectEntry(ReorderableList list)
{
m_KeywordSelectedIndex = list.index;
}
// Allowed indicies are 1-MAX_ENUM_ENTRIES
int GetFirstUnusedKeywordID()
{
if (!(shaderInput is ShaderKeyword keyword))
return 0;
List<int> unusedIDs = new List<int>();
foreach (KeywordEntry keywordEntry in keyword.entries)
{
unusedIDs.Add(keywordEntry.id);
}
for (int x = 1; x <= KeywordNode.k_MaxEnumEntries; x++)
{
if (!unusedIDs.Contains(x))
return x;
}
Debug.LogError("GetFirstUnusedID: Attempting to get unused ID when all IDs are used.");
return -1;
}
void KeywordAddEntry(ReorderableList list)
{
if (!(shaderInput is ShaderKeyword keyword))
return;
this._preChangeValueCallback("Add Keyword Entry");
int index = GetFirstUnusedKeywordID();
if (index <= 0)
return; // Error has already occured, don't attempt to add this entry.
var displayName = "New";
var referenceName = "NEW";
GetDuplicateSafeEnumNames(index, "New", out displayName, out referenceName);
// Add new entry
keyword.entries.Add(new KeywordEntry(index, displayName, referenceName));
// Update GUI
this._postChangeValueCallback(true);
this._keywordChangedCallback();
m_KeywordSelectedIndex = list.list.Count - 1;
}
void KeywordRemoveEntry(ReorderableList list)
{
if (!(shaderInput is ShaderKeyword keyword))
return;
this._preChangeValueCallback("Remove Keyword Entry");
// Remove entry
m_KeywordSelectedIndex = list.index;
var selectedEntry = (KeywordEntry)m_KeywordReorderableList.list[list.index];
keyword.entries.Remove(selectedEntry);
// Clamp value within new entry range
int value = Mathf.Clamp(keyword.value, 0, keyword.entries.Count - 1);
keyword.value = value;
// Rebuild();
this._postChangeValueCallback(true);
this._keywordChangedCallback();
m_KeywordSelectedIndex = m_KeywordSelectedIndex >= list.list.Count - 1 ? list.list.Count - 1 : m_KeywordSelectedIndex;
}
void KeywordReorderEntries(ReorderableList list)
{
this._preChangeValueCallback("Reorder Keyword Entry");
this._postChangeValueCallback(true);
}
public string GetDuplicateSafeEnumDisplayName(int id, string name)
{
name = name.Trim();
var entryList = m_KeywordReorderableList.list as List<KeywordEntry>;
return GraphUtil.SanitizeName(entryList.Where(p => p.id != id).Select(p => p.displayName), "{0} {1}", name, m_DisplayNameDisallowedPattern);
}
void GetDuplicateSafeEnumNames(int id, string name, out string displayName, out string referenceName)
{
name = name.Trim();
// Get de-duplicated display and reference names
displayName = GetDuplicateSafeEnumDisplayName(id, name);
referenceName = GetDuplicateSafeReferenceName(id, displayName.ToUpper());
// Check when the simple reference name should be for the display name.
// If these don't match then there will be a desync which causes the enum entry to not work.
// An example where this happens is ["new 1", "NEW_1"] already exists.
// The display name "New_1" is added.
// This new display name doesn't exist, but it finds the reference name of "NEW_1" already exists so we get the pair ["New_1", "NEW_2"] which is invalid.
// The easiest fix in this case is to just use the safe reference name as the new display name which is guaranteed to be unique.
var simpleReferenceName = Regex.Replace(displayName.ToUpper(), m_ReferenceNameDisallowedPattern, "_");
if (referenceName != simpleReferenceName)
displayName = referenceName;
}
string GetSanitizedDisplayName(string name)
{
name = name.Trim();
return Regex.Replace(name, m_DisplayNameDisallowedPattern, "_");
}
public string GetDuplicateSafeReferenceName(int id, string name)
{
name = name.Trim();
var entryList = m_KeywordReorderableList.list as List<KeywordEntry>;
return GraphUtil.SanitizeName(entryList.Where(p => p.id != id).Select(p => p.referenceName), "{0}_{1}", name, m_ReferenceNameDisallowedPattern);
}
string GetSanitizedReferenceName(string name)
{
name = name.Trim();
return Regex.Replace(name, m_ReferenceNameDisallowedPattern, "_");
}
string GetSanitizedEnumRefName(string value)
{
value = value.Trim();
return Regex.Replace(value, m_EnumRefDisallowedPattern, "_");
}
string GetSanitizedAttributeValue(string value)
{
var values = value.Split(',', StringSplitOptions.RemoveEmptyEntries);
if (values.Length == 0)
{
return string.Empty;
}
else
{
for (int i = 0; i < values.Length; i++)
{
values[i] = Regex.Replace(values[i], m_AttributeValueDisallowedPattern, "_").Trim();
}
return string.Join(", ", values);
}
}
int FindDuplicateKeywordReferenceNameIndex(int id, string referenceName)
{
var entryList = m_KeywordReorderableList.list as List<KeywordEntry>;
return entryList.FindIndex(entry => entry.id != id && entry.referenceName == referenceName);
}
void BuildDropdownFields(PropertySheet propertySheet, ShaderInput shaderInput)
{
var dropdown = shaderInput as ShaderDropdown;
if (dropdown == null)
return;
BuildDropdownField(propertySheet, dropdown);
}
void BuildDropdownField(PropertySheet propertySheet, ShaderDropdown dropdown)
{
// Clamp value between entry list
int value = Mathf.Clamp(dropdown.value, 0, dropdown.entries.Count - 1);
// Default field
var field = new PopupField<string>(dropdown.entries.Select(x => x.displayName).ToList(), value);
field.RegisterValueChangedCallback(evt =>
{
this._preChangeValueCallback("Change Dropdown Value");
dropdown.value = field.index;
m_DropdownId = dropdown.entryId;
this._postChangeValueCallback(false, ModificationScope.Graph);
});
AddPropertyRowToSheet(propertySheet, field, "Default");
var container = new IMGUIContainer(() => OnDropdownGUIHandler()) { name = "ListContainer" };
AddPropertyRowToSheet(propertySheet, container, "Entries");
container.SetEnabled(true);
}
void OnDropdownGUIHandler()
{
if (m_DropdownReorderableList == null)
{
DropdownRecreateList();
DropdownAddCallbacks();
}
m_DropdownReorderableList.index = m_DropdownSelectedIndex;
m_DropdownReorderableList.DoLayoutList();
}
internal void DropdownRecreateList()
{
if (!(shaderInput is ShaderDropdown dropdown))
return;
// Create reorderable list from entries
m_DropdownReorderableList = new ReorderableList(dropdown.entries, typeof(DropdownEntry), true, true, true, true);
m_Dropdown = dropdown;
m_DropdownId = dropdown.entryId;
}
void DropdownAddCallbacks()
{
if (!(shaderInput is ShaderDropdown dropdown))
return;
// Draw Header
m_DropdownReorderableList.drawHeaderCallback = (Rect rect) =>
{
int indent = 14;
var displayRect = new Rect(rect.x + indent, rect.y, (rect.width - indent) / 2, rect.height);
EditorGUI.LabelField(displayRect, "Entry Name");
};
// Draw Element
m_DropdownReorderableList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
{
DropdownEntry entry = ((DropdownEntry)m_DropdownReorderableList.list[index]);
EditorGUI.BeginChangeCheck();
Rect displayRect = new Rect(rect.x, rect.y, rect.width / 2, EditorGUIUtility.singleLineHeight);
var displayName = EditorGUI.DelayedTextField(displayRect, entry.displayName, EditorStyles.label);
if (EditorGUI.EndChangeCheck())
{
displayName = GetSanitizedDisplayName(displayName);
var duplicateIndex = FindDuplicateDropdownDisplayNameIndex(entry.id, displayName);
if (duplicateIndex != -1)
{
var duplicateEntry = ((DropdownEntry)m_DropdownReorderableList.list[duplicateIndex]);
Debug.LogWarning($"Display name '{displayName}' will create the same display name as entry {duplicateIndex + 1}.");
}
else if (string.IsNullOrWhiteSpace(displayName))
Debug.LogWarning("Invalid display name. Display names cannot be empty or all whitespace.");
else if (int.TryParse(displayName, out int intVal) || float.TryParse(displayName, out float floatVal))
Debug.LogWarning("Invalid display name. Display names cannot be valid integer or floating point numbers.");
else
dropdown.entries[index] = new DropdownEntry(GetFirstUnusedDropdownID(), displayName);
this._postChangeValueCallback(true);
}
};
// Element height
m_DropdownReorderableList.elementHeightCallback = (int indexer) =>
{
return m_DropdownReorderableList.elementHeight;
};
// Can add
m_DropdownReorderableList.onCanAddCallback = (ReorderableList list) =>
{
return true;
};
// Can remove
m_DropdownReorderableList.onCanRemoveCallback = (ReorderableList list) =>
{
return list.count > DropdownNode.k_MinEnumEntries;
};
// Add callback delegates
m_DropdownReorderableList.onSelectCallback += DropdownSelectEntry;
m_DropdownReorderableList.onAddCallback += DropdownAddEntry;
m_DropdownReorderableList.onRemoveCallback += DropdownRemoveEntry;
m_DropdownReorderableList.onReorderCallback += DropdownReorderEntries;
}
void DropdownSelectEntry(ReorderableList list)
{
m_DropdownSelectedIndex = list.index;
}
int GetFirstUnusedDropdownID()
{
if (!(shaderInput is ShaderDropdown dropdown))
return 0;
List<int> ids = new List<int>();
foreach (DropdownEntry dropdownEntry in dropdown.entries)
{
ids.Add(dropdownEntry.id);
}
for (int x = 1; ; x++)
{
if (!ids.Contains(x))
return x;
}
}
void DropdownAddEntry(ReorderableList list)
{
if (!(shaderInput is ShaderDropdown dropdown))
return;
this._preChangeValueCallback("Add Dropdown Entry");
int index = GetFirstUnusedDropdownID();
var displayName = GetDuplicateSafeDropdownDisplayName(index, "New");
// Add new entry
dropdown.entries.Add(new DropdownEntry(index, displayName));
// Update GUI
this._postChangeValueCallback(true);
this._dropdownChangedCallback();
m_DropdownSelectedIndex = list.list.Count - 1;
}
void DropdownRemoveEntry(ReorderableList list)
{
if (!(shaderInput is ShaderDropdown dropdown))
return;
this._preChangeValueCallback("Remove Dropdown Entry");
// Remove entry
m_DropdownSelectedIndex = list.index;
var selectedEntry = (DropdownEntry)m_DropdownReorderableList.list[list.index];
dropdown.entries.Remove(selectedEntry);
// Clamp value within new entry range
int value = Mathf.Clamp(dropdown.value, 0, dropdown.entries.Count - 1);
dropdown.value = value;
this._postChangeValueCallback(true);
this._dropdownChangedCallback();
m_DropdownSelectedIndex = m_DropdownSelectedIndex >= list.list.Count - 1 ? list.list.Count - 1 : m_DropdownSelectedIndex;
}
void DropdownReorderEntries(ReorderableList list)
{
var index = m_Dropdown.IndexOfId(m_DropdownId);
if (index != m_Dropdown.value)
m_Dropdown.value = index;
this._postChangeValueCallback(true);
}
public string GetDuplicateSafeDropdownDisplayName(int id, string name)
{
var entryList = m_DropdownReorderableList.list as List<DropdownEntry>;
return GraphUtil.SanitizeName(entryList.Where(p => p.id != id).Select(p => p.displayName), "{0} {1}", name, m_DisplayNameDisallowedPattern);
}
int FindDuplicateDropdownDisplayNameIndex(int id, string displayName)
{
var entryList = m_DropdownReorderableList.list as List<DropdownEntry>;
return entryList.FindIndex(entry => entry.id != id && entry.displayName == displayName);
}
}
}