// Amplify Shader Editor - Visual Shader Editing Tool // Copyright (c) Amplify Creations, Lda using System; using System.IO; using System.Collections.Generic; using UnityEditor; using UnityEngine; using UnityEditorInternal; namespace AmplifyShaderEditor { public enum AdditionalLineType { Include, Define, Pragma, Custom } [Serializable] public class AdditionalDirectiveContainerSaveItem { public AdditionalLineType LineType = AdditionalLineType.Include; public string LineValue = string.Empty; public bool GUIDToggle = false; public string GUIDValue = string.Empty; public AdditionalDirectiveContainerSaveItem( AdditionalLineType lineType, string lineValue, bool guidToggle, string guidValue ) { LineType = lineType; LineValue = lineValue; GUIDToggle = guidToggle; GUIDValue = guidValue; } public AdditionalDirectiveContainerSaveItem( AdditionalDirectiveContainer container ) { LineType = container.LineType; LineValue = container.LineValue; GUIDToggle = container.GUIDToggle; GUIDValue = container.GUIDValue; } } [Serializable] public class AdditionalDirectiveContainer : ScriptableObject { public AdditionalLineType LineType = AdditionalLineType.Include; public string LineValue = string.Empty; public bool GUIDToggle = false; public string GUIDValue = string.Empty; public TextAsset LibObject = null; public void Init( AdditionalDirectiveContainerSaveItem item ) { LineType = item.LineType; LineValue = item.LineValue; GUIDToggle = item.GUIDToggle; GUIDValue = item.GUIDValue; if( GUIDToggle ) { LibObject = AssetDatabase.LoadAssetAtPath( AssetDatabase.GUIDToAssetPath( GUIDValue ) ); } } public void OnDestroy() { //Debug.Log( "Destoying directives" ); LibObject = null; } public string Value { get { switch( LineType ) { case AdditionalLineType.Include: { if( GUIDToggle ) { string shaderPath = AssetDatabase.GUIDToAssetPath( GUIDValue ); if( !string.IsNullOrEmpty( shaderPath ) ) return shaderPath; } return LineValue; } case AdditionalLineType.Define: return LineValue; case AdditionalLineType.Pragma: return LineValue; } return LineValue; } } public string FormattedValue { get { switch( LineType ) { case AdditionalLineType.Include: { if( GUIDToggle ) { string shaderPath = AssetDatabase.GUIDToAssetPath( GUIDValue ); if( !string.IsNullOrEmpty( shaderPath ) ) return string.Format( Constants.IncludeFormat, shaderPath ); } return string.Format( Constants.IncludeFormat, LineValue ); } case AdditionalLineType.Define: return string.Format( Constants.DefineFormat, LineValue ); case AdditionalLineType.Pragma: return string.Format( Constants.PragmaFormat, LineValue ); } return LineValue; } } } public enum ReordableAction { None, Add, Remove } [Serializable] public sealed class TemplateAdditionalDirectivesHelper : TemplateModuleParent { private string NativeFoldoutStr = "Native"; [SerializeField] private List m_additionalDirectives = new List(); [SerializeField] private List m_shaderFunctionDirectives = new List(); [SerializeField] private List m_nativeDirectives = new List(); [SerializeField] private bool m_nativeDirectivesFoldout = false; //ONLY USED BY SHADER FUNCTIONS // Since AdditionalDirectiveContainer must be a ScriptableObject because of serialization shenanigans it will not serialize the info correctly into the shader function when saving it into a file ( it only saves the id ) // For it to properly work, each AdditionalDirectiveContainer should be added to the SF asset, but that would make it to have children ( which are seen on the project inspector ) // Must revisit this later on and come up with a proper solution [SerializeField] private List m_directivesSaveItems = new List(); private ReordableAction m_actionType = ReordableAction.None; private int m_actionIndex = 0; private ReorderableList m_reordableList = null; private GUIStyle m_propertyAdjustment; private UndoParentNode m_currOwner; public TemplateAdditionalDirectivesHelper( string moduleName ) : base( moduleName ) { } //public void AddShaderFunctionItem( AdditionalLineType type, string item ) //{ // UpdateShaderFunctionDictionary(); // string id = type + item; // if( !m_shaderFunctionDictionary.ContainsKey( id ) ) // { // AdditionalDirectiveContainer newItem = ScriptableObject.CreateInstance(); // newItem.LineType = type; // newItem.LineValue = item; // newItem.hideFlags = HideFlags.HideAndDontSave; // m_shaderFunctionDirectives.Add( newItem ); // m_shaderFunctionDictionary.Add( id, newItem ); // } //} public void AddShaderFunctionItems( List functionList ) { if( functionList.Count > 0 ) m_shaderFunctionDirectives.AddRange( functionList ); } public void RemoveShaderFunctionItems( List functionList ) { for( int i = 0; i < functionList.Count; i++ ) { m_shaderFunctionDirectives.Remove( functionList[ i ] ); } } //public void RemoveShaderFunctionItem( AdditionalLineType type, string item ) //{ // m_shaderFunctionDirectives.RemoveAll( x => x.LineType == type && x.LineValue.Equals( item ) ); //} public void AddItems( AdditionalLineType type, List items ) { int count = items.Count; for( int i = 0; i < count; i++ ) { AdditionalDirectiveContainer newItem = ScriptableObject.CreateInstance(); newItem.LineType = type; newItem.LineValue = items[ i ]; newItem.hideFlags = HideFlags.HideAndDontSave; m_additionalDirectives.Add( newItem ); } } public void FillNativeItems( List nativeItems ) { m_nativeDirectives.Clear(); m_nativeDirectives.AddRange( nativeItems ); } void DrawNativeItems() { EditorGUILayout.Separator(); EditorGUI.indentLevel++; int count = m_nativeDirectives.Count; for( int i = 0; i < count; i++ ) { EditorGUILayout.LabelField( m_nativeDirectives[ i ] ); } EditorGUI.indentLevel--; EditorGUILayout.Separator(); } void DrawButtons() { EditorGUILayout.Separator(); // Add keyword if( GUILayout.Button( string.Empty, UIUtils.PlusStyle, GUILayout.Width( Constants.PlusMinusButtonLayoutWidth ) ) ) { AdditionalDirectiveContainer newItem = ScriptableObject.CreateInstance(); newItem.hideFlags = HideFlags.HideAndDontSave; m_additionalDirectives.Add( newItem ); EditorGUI.FocusTextInControl( null ); m_isDirty = true; } //Remove keyword if( GUILayout.Button( string.Empty, UIUtils.MinusStyle, GUILayout.Width( Constants.PlusMinusButtonLayoutWidth ) ) ) { if( m_additionalDirectives.Count > 0 ) { AdditionalDirectiveContainer itemToDelete = m_additionalDirectives[ m_additionalDirectives.Count - 1 ]; m_additionalDirectives.RemoveAt( m_additionalDirectives.Count - 1 ); ScriptableObject.DestroyImmediate( itemToDelete ); EditorGUI.FocusTextInControl( null ); } m_isDirty = true; } } public override void Draw( UndoParentNode currOwner, bool style = true ) { m_currOwner = currOwner; if( m_reordableList == null ) { m_reordableList = new ReorderableList( m_additionalDirectives, typeof( AdditionalDirectiveContainer ), true, false, false, false ) { headerHeight = 0, footerHeight = 0, showDefaultBackground = false, drawElementCallback = ( Rect rect, int index, bool isActive, bool isFocused ) => { if( m_additionalDirectives[ index ] != null ) { float labelWidthStyleAdjust = 0; if( style ) { rect.xMin -= 10; labelWidthStyleAdjust = 15; } else { rect.xMin -= 1; } float popUpWidth = style ? 75 : 60f; float widthAdjust = m_additionalDirectives[ index ].LineType == AdditionalLineType.Include ? -14 : 0; Rect popupPos = new Rect( rect.x, rect.y, popUpWidth, EditorGUIUtility.singleLineHeight ); Rect GUIDTogglePos = m_additionalDirectives[ index ].LineType == AdditionalLineType.Include ? new Rect( rect.x + rect.width - 3 * Constants.PlusMinusButtonLayoutWidth, rect.y, Constants.PlusMinusButtonLayoutWidth, Constants.PlusMinusButtonLayoutWidth ) : new Rect(); Rect buttonPlusPos = new Rect( rect.x + rect.width - 2 * Constants.PlusMinusButtonLayoutWidth, rect.y - 2, Constants.PlusMinusButtonLayoutWidth, Constants.PlusMinusButtonLayoutWidth ); Rect buttonMinusPos = new Rect( rect.x + rect.width - Constants.PlusMinusButtonLayoutWidth, rect.y - 2, Constants.PlusMinusButtonLayoutWidth, Constants.PlusMinusButtonLayoutWidth ); float labelWidthBuffer = EditorGUIUtility.labelWidth; Rect labelPos = new Rect( rect.x + popupPos.width - labelWidthStyleAdjust, rect.y, labelWidthStyleAdjust + rect.width - popupPos.width - buttonPlusPos.width - buttonMinusPos.width + widthAdjust, EditorGUIUtility.singleLineHeight ); m_additionalDirectives[ index ].LineType = (AdditionalLineType)m_currOwner.EditorGUIEnumPopup( popupPos, m_additionalDirectives[ index ].LineType ); if( m_additionalDirectives[ index ].LineType == AdditionalLineType.Include ) { if( m_additionalDirectives[ index ].GUIDToggle ) { //if( m_additionalDirectives[ index ].LibObject == null && !string.IsNullOrEmpty( m_additionalDirectives[ index ].GUIDValue ) ) //{ // m_additionalDirectives[ index ].LibObject = AssetDatabase.LoadAssetAtPath( AssetDatabase.GUIDToAssetPath( m_additionalDirectives[ index ].GUIDValue ) ); //} EditorGUI.BeginChangeCheck(); TextAsset obj = m_currOwner.EditorGUIObjectField( labelPos, m_additionalDirectives[ index ].LibObject, typeof( TextAsset ), false ) as TextAsset; if( EditorGUI.EndChangeCheck() ) { string pathName = AssetDatabase.GetAssetPath( obj ); string extension = Path.GetExtension( pathName ); extension = extension.ToLower(); if( extension.Equals( ".cginc" ) || extension.Equals( ".hlsl" ) ) { m_additionalDirectives[ index ].LibObject = obj; m_additionalDirectives[ index ].GUIDValue = AssetDatabase.AssetPathToGUID( pathName ); } } } else { m_additionalDirectives[ index ].LineValue = m_currOwner.EditorGUITextField( labelPos, string.Empty, m_additionalDirectives[ index ].LineValue ); } if( GUI.Button( GUIDTogglePos, m_additionalDirectives[ index ].GUIDToggle ? UIUtils.FloatIntIconOFF : UIUtils.FloatIntIconON, UIUtils.FloatIntPickerONOFF ) ) m_additionalDirectives[ index ].GUIDToggle = !m_additionalDirectives[ index ].GUIDToggle; } else { m_additionalDirectives[ index ].LineValue = m_currOwner.EditorGUITextField( labelPos, string.Empty, m_additionalDirectives[ index ].LineValue ); } if( GUI.Button( buttonPlusPos, string.Empty, UIUtils.PlusStyle ) ) { m_actionType = ReordableAction.Add; m_actionIndex = index; } if( GUI.Button( buttonMinusPos, string.Empty, UIUtils.MinusStyle ) ) { m_actionType = ReordableAction.Remove; m_actionIndex = index; } } } }; } if( m_actionType != ReordableAction.None ) { switch( m_actionType ) { case ReordableAction.Add: AdditionalDirectiveContainer newItem = ScriptableObject.CreateInstance(); newItem.hideFlags = HideFlags.HideAndDontSave; m_additionalDirectives.Insert( m_actionIndex + 1, newItem ); break; case ReordableAction.Remove: AdditionalDirectiveContainer itemToDelete = m_additionalDirectives[ m_actionIndex ]; m_additionalDirectives.RemoveAt( m_actionIndex ); ScriptableObject.DestroyImmediate( itemToDelete ); break; } m_isDirty = true; m_actionType = ReordableAction.None; EditorGUI.FocusTextInControl( null ); } bool foldoutValue = currOwner.ContainerGraph.ParentWindow.InnerWindowVariables.ExpandedAdditionalDirectives; if( style ) { NodeUtils.DrawPropertyGroup( ref foldoutValue, m_moduleName, DrawReordableList, DrawButtons ); } else { NodeUtils.DrawNestedPropertyGroup( ref foldoutValue, m_moduleName, DrawReordableList, DrawButtons ); } currOwner.ContainerGraph.ParentWindow.InnerWindowVariables.ExpandedAdditionalDirectives = foldoutValue; } void DrawReordableList() { if( m_reordableList != null ) { if( m_propertyAdjustment == null ) { m_propertyAdjustment = new GUIStyle(); m_propertyAdjustment.padding.left = 17; } //EditorGUILayout.BeginVertical( m_propertyAdjustment ); EditorGUILayout.Space(); if( m_nativeDirectives.Count > 0 ) { NodeUtils.DrawNestedPropertyGroup( ref m_nativeDirectivesFoldout, NativeFoldoutStr, DrawNativeItems, 4 ); } if( m_additionalDirectives.Count == 0 ) { EditorGUILayout.HelpBox( "Your list is Empty!\nUse the plus button to add one.", MessageType.Info ); } else { m_reordableList.DoLayoutList(); } EditorGUILayout.Space(); //EditorGUILayout.EndVertical(); } } public void AddAllToDataCollector( ref MasterNodeDataCollector dataCollector, TemplateIncludePragmaContainter nativesContainer ) { AddToDataCollector( ref dataCollector, nativesContainer, false ); AddToDataCollector( ref dataCollector, nativesContainer, true ); } public void AddAllToDataCollector( ref MasterNodeDataCollector dataCollector ) { AddToDataCollector( ref dataCollector, false ); AddToDataCollector( ref dataCollector, true ); } void AddToDataCollector( ref MasterNodeDataCollector dataCollector, TemplateIncludePragmaContainter nativesContainer, bool fromSF ) { List list = fromSF ? m_shaderFunctionDirectives : m_additionalDirectives; int count = list.Count; for( int i = 0; i < count; i++ ) { switch( list[ i ].LineType ) { case AdditionalLineType.Include: { string value = list[ i ].Value; if( !string.IsNullOrEmpty( value ) && !nativesContainer.HasInclude( value ) ) { dataCollector.AddToMisc( list[ i ].FormattedValue ); } } break; case AdditionalLineType.Define: { if( !string.IsNullOrEmpty( list[ i ].LineValue ) && !nativesContainer.HasDefine( list[ i ].LineValue ) ) { dataCollector.AddToMisc( list[ i ].FormattedValue ); } } break; case AdditionalLineType.Pragma: { if( !string.IsNullOrEmpty( list[ i ].LineValue ) && !nativesContainer.HasPragma( list[ i ].LineValue ) ) { dataCollector.AddToMisc( list[ i ].FormattedValue ); } } break; default: case AdditionalLineType.Custom: dataCollector.AddToMisc( list[ i ].LineValue ); break; } } } void AddToDataCollector( ref MasterNodeDataCollector dataCollector, bool fromSF ) { List list = fromSF ? m_shaderFunctionDirectives : m_additionalDirectives; int count = list.Count; for( int i = 0; i < count; i++ ) { switch( list[ i ].LineType ) { case AdditionalLineType.Include: { string value = list[ i ].FormattedValue; if( !string.IsNullOrEmpty( value ) ) { dataCollector.AddToMisc( value ); } } break; case AdditionalLineType.Define: { if( !string.IsNullOrEmpty( list[ i ].LineValue ) ) { dataCollector.AddToMisc( list[ i ].FormattedValue ); } } break; case AdditionalLineType.Pragma: { if( !string.IsNullOrEmpty( list[ i ].LineValue ) ) { dataCollector.AddToMisc( list[ i ].FormattedValue ); } } break; default: case AdditionalLineType.Custom: dataCollector.AddToMisc( list[ i ].LineValue ); break; } } } public override void ReadFromString( ref uint index, ref string[] nodeParams ) { try { int count = Convert.ToInt32( nodeParams[ index++ ] ); for( int i = 0; i < count; i++ ) { AdditionalLineType lineType = (AdditionalLineType)Enum.Parse( typeof( AdditionalLineType ), nodeParams[ index++ ] ); string lineValue = nodeParams[ index++ ]; AdditionalDirectiveContainer newItem = ScriptableObject.CreateInstance(); newItem.hideFlags = HideFlags.HideAndDontSave; newItem.LineType = lineType; newItem.LineValue = lineValue.Replace( Constants.SemiColonSeparator, ';' ); if( UIUtils.CurrentShaderVersion() > 15607 ) { newItem.GUIDToggle = Convert.ToBoolean( nodeParams[ index++ ] ); newItem.GUIDValue = nodeParams[ index++ ]; if( newItem.GUIDToggle ) { newItem.LibObject = AssetDatabase.LoadAssetAtPath( AssetDatabase.GUIDToAssetPath( newItem.GUIDValue ) ); if( newItem.LibObject == null ) { Debug.LogWarning( "Include file not found with GUID " + newItem.GUIDValue ); } } } m_additionalDirectives.Add( newItem ); } } catch( Exception e ) { Debug.LogException( e ); } } public override void WriteToString( ref string nodeInfo ) { IOUtils.AddFieldValueToString( ref nodeInfo, m_additionalDirectives.Count ); for( int i = 0; i < m_additionalDirectives.Count; i++ ) { IOUtils.AddFieldValueToString( ref nodeInfo, m_additionalDirectives[ i ].LineType ); IOUtils.AddFieldValueToString( ref nodeInfo, m_additionalDirectives[ i ].LineValue.Replace( ';', Constants.SemiColonSeparator ) ); IOUtils.AddFieldValueToString( ref nodeInfo, m_additionalDirectives[ i ].GUIDToggle ); IOUtils.AddFieldValueToString( ref nodeInfo, m_additionalDirectives[ i ].GUIDValue ); } } // read comment on m_directivesSaveItems declaration public void UpdateSaveItemsFromDirectives() { bool foundNull = false; m_directivesSaveItems.Clear(); for( int i = 0; i < m_additionalDirectives.Count; i++ ) { if( m_additionalDirectives[ i ] != null ) { m_directivesSaveItems.Add( new AdditionalDirectiveContainerSaveItem( m_additionalDirectives[ i ] ) ); } else { foundNull = true; } } if( foundNull ) { m_additionalDirectives.RemoveAll( item => item == null ); } } public void CleanNullDirectives() { m_additionalDirectives.RemoveAll( item => item == null ); } // read comment on m_directivesSaveItems declaration public void UpdateDirectivesFromSaveItems() { if( m_directivesSaveItems.Count > 0 ) { for( int i = 0; i < m_additionalDirectives.Count; i++ ) { if( m_additionalDirectives[ i ] != null ) ScriptableObject.DestroyImmediate( m_additionalDirectives[ i ] ); } m_additionalDirectives.Clear(); for( int i = 0; i < m_directivesSaveItems.Count; i++ ) { AdditionalDirectiveContainer newItem = ScriptableObject.CreateInstance(); newItem.hideFlags = HideFlags.HideAndDontSave; newItem.Init( m_directivesSaveItems[ i ] ); m_additionalDirectives.Add( newItem ); } m_directivesSaveItems.Clear(); } } public override void Destroy() { base.Destroy(); m_nativeDirectives.Clear(); m_nativeDirectives = null; for( int i = 0; i < m_additionalDirectives.Count; i++ ) { ScriptableObject.DestroyImmediate( m_additionalDirectives[ i ] ); } m_additionalDirectives.Clear(); m_additionalDirectives = null; m_propertyAdjustment = null; m_reordableList = null; } public List DirectivesList { get { return m_additionalDirectives; } } public bool IsValid { get { return m_validData; } set { m_validData = value; } } } }