using System; using System.Collections.Generic; using UnityEditor.Graphing; using UnityEditor.ShaderGraph.Serialization; using UnityEngine; namespace UnityEditor.ShaderGraph.Internal { public abstract class ShaderInput : JsonObject { [SerializeField] SerializableGuid m_Guid = new SerializableGuid(); internal Guid guid => m_Guid.guid; internal void OverrideGuid(string namespaceId, string name) { m_Guid.guid = GenerateNamespaceUUID(namespaceId, name); } [SerializeField] string m_Name; public string displayName { get { if (string.IsNullOrEmpty(m_Name)) return $"{concreteShaderValueType}_{objectId}"; return m_Name; } set { // this is a raw set of the display name // if you want to a fully graph-connected set-and-sanitize-and-update, // call SetDisplayNameAndSanitizeForGraph() instead m_Name = value; } } // This delegate and the associated callback can be bound to in order for any one that cares about display name changes to be notified internal delegate void ChangeDisplayNameCallback(string newDisplayName); ChangeDisplayNameCallback m_displayNameUpdateTrigger; internal ChangeDisplayNameCallback displayNameUpdateTrigger { get => m_displayNameUpdateTrigger; set => m_displayNameUpdateTrigger = value; } // sanitizes the desired name according to the current graph, and assigns it as the display name // also calls the update trigger to update other bits of the graph UI that use the name internal void SetDisplayNameAndSanitizeForGraph(GraphData graphData, string desiredName = null) { string originalDisplayName = displayName; // if no desired name passed in, sanitize the current name if (desiredName == null) desiredName = originalDisplayName; var sanitizedName = graphData.SanitizeGraphInputName(this, desiredName); bool changed = (originalDisplayName != sanitizedName); // only assign if it was changed if (changed) m_Name = sanitizedName; // update the default reference name UpdateDefaultReferenceName(graphData); // Updates any views associated with this input so that display names stay up to date // NOTE: we call this even when the name has not changed, because there may be fields // that were user-edited and still have the temporary desired name -- those must update if (m_displayNameUpdateTrigger != null) { m_displayNameUpdateTrigger.Invoke(m_Name); } } internal void SetReferenceNameAndSanitizeForGraph(GraphData graphData, string desiredRefName = null) { string originalRefName = referenceName; // if no desired ref name, use the current name if (string.IsNullOrEmpty(desiredRefName)) desiredRefName = originalRefName; // sanitize and deduplicate the desired name var sanitizedRefName = graphData.SanitizeGraphInputReferenceName(this, desiredRefName); // check if the final result is different from the current name bool changed = (originalRefName != sanitizedRefName); // if changed, then set the new name up as an override if (changed) overrideReferenceName = sanitizedRefName; } // resets the reference name to a "default" value (deduplicated against existing reference names) // returns the new default reference name internal string ResetReferenceName(GraphData graphData) { overrideReferenceName = null; // because we are clearing an override, we must force a sanitization pass on the default ref name // as there may now be collisions that didn't previously exist UpdateDefaultReferenceName(graphData, true); return referenceName; } internal void UpdateDefaultReferenceName(GraphData graphData, bool forceSanitize = false) { if (m_DefaultRefNameVersion <= 0) return; // old version is updated in the getter if (forceSanitize || string.IsNullOrEmpty(m_DefaultReferenceName) || (m_RefNameGeneratedByDisplayName != displayName)) { // Make sure all reference names are consistently auto-generated with a pre-pended underscore (if they can be renamed) var targetRefName = displayName; if (this.isReferenceRenamable && !targetRefName.StartsWith("_")) targetRefName = "_" + targetRefName; m_DefaultReferenceName = graphData.SanitizeGraphInputReferenceName(this, targetRefName); m_RefNameGeneratedByDisplayName = displayName; } } const int k_LatestDefaultRefNameVersion = 1; // this is used to know whether this shader input is using: // 0) the "old" default reference naming scheme (type + GUID) // 1) the new default reference naming scheme (make it similar to the display name) [SerializeField] int m_DefaultRefNameVersion = k_LatestDefaultRefNameVersion; [SerializeField] string m_RefNameGeneratedByDisplayName; // used to tell what was the display name used to generate the default reference name [SerializeField] string m_DefaultReferenceName; // NOTE: this can be NULL for old graphs, or newly created properties public string referenceName { get { if (string.IsNullOrEmpty(overrideReferenceName)) { if (m_DefaultRefNameVersion == 0) { if (string.IsNullOrEmpty(m_DefaultReferenceName)) m_DefaultReferenceName = GetOldDefaultReferenceName(); return m_DefaultReferenceName; } else // version 1 { // default reference name is updated elsewhere in the new naming scheme return m_DefaultReferenceName; } } return overrideReferenceName; } } public virtual string referenceNameForEditing => referenceName; public override void OnBeforeDeserialize() { // if serialization doesn't write to m_DefaultRefNameVersion, then it is an old shader input, and should use the old default naming scheme m_DefaultRefNameVersion = 0; base.OnBeforeDeserialize(); } // This is required to handle Material data serialized with "_Color_GUID" reference names // m_DefaultReferenceName expects to match the material data and previously used PropertyType // ColorShaderProperty is the only case where PropertyType doesn't match ConcreteSlotValueType public virtual string GetOldDefaultReferenceName() { return $"{concreteShaderValueType.ToString()}_{objectId}"; } // returns true if this shader input is CURRENTLY using the old default reference name public bool IsUsingOldDefaultRefName() { return string.IsNullOrEmpty(overrideReferenceName) && (m_DefaultRefNameVersion == 0); } // returns true if this shader input is CURRENTLY using the new default reference name public bool IsUsingNewDefaultRefName() { return string.IsNullOrEmpty(overrideReferenceName) && (m_DefaultRefNameVersion >= 1); } // upgrades the default reference name to use the new naming scheme internal string UpgradeDefaultReferenceName(GraphData graphData) { m_DefaultRefNameVersion = k_LatestDefaultRefNameVersion; m_DefaultReferenceName = null; m_RefNameGeneratedByDisplayName = null; UpdateDefaultReferenceName(graphData, true); // make sure to sanitize the new default return referenceName; } [SerializeField] string m_OverrideReferenceName; internal string overrideReferenceName { get => m_OverrideReferenceName; set => m_OverrideReferenceName = value; } [SerializeField] bool m_GeneratePropertyBlock = true; internal bool generatePropertyBlock // this is basically the "exposed" toggle { get => m_GeneratePropertyBlock; set => m_GeneratePropertyBlock = value; } internal bool isExposed => isExposable && generatePropertyBlock; public virtual bool allowedInSubGraph { get { return true; } } public virtual bool allowedInMainGraph { get { return true; } } internal abstract ConcreteSlotValueType concreteShaderValueType { get; } internal abstract bool isExposable { get; } internal virtual bool isAlwaysExposed => false; // this controls whether the UI allows the user to rename the display and reference names internal abstract bool isRenamable { get; } internal virtual bool isReferenceRenamable => isRenamable; internal virtual bool isCustomSlotAllowed => true; [SerializeField] bool m_UseCustomSlotLabel = false; [SerializeField] string m_CustomSlotLabel; internal bool useCustomSlotLabel { get => m_UseCustomSlotLabel; set => m_UseCustomSlotLabel = value; } internal string customSlotLabel { get => m_CustomSlotLabel; set => m_CustomSlotLabel = value; } internal bool isConnectionTestable { get => m_UseCustomSlotLabel; } static internal string GetConnectionStateVariableName(string variableName) { return variableName + "_IsConnected"; } internal abstract ShaderInput Copy(); internal virtual void OnBeforePasteIntoGraph(GraphData graph) { } } }