Exemplo n.º 1
0
        private static void GetProperties(ref List <TextureProp> m_Properties, ShaderImporter importer)
        {
            var shader        = importer.GetShader();
            var propertyCount = ShaderUtil.GetPropertyCount(shader);

            for (var i = 0; i < propertyCount; i++)
            {
                if (ShaderUtil.GetPropertyType(shader, i) != ShaderUtil.ShaderPropertyType.TexEnv)
                {
                    continue;
                }

                var propertyName = ShaderUtil.GetPropertyName(shader, i);
                var displayName  = ShaderUtil.GetPropertyDescription(shader, i);                 // might be empty
                var texture      = importer.GetDefaultTexture(propertyName);

                var assetBundleName = "";
                if (texture != null)
                {
                    var textureAssetPath = AssetDatabase.GetAssetPath(texture);
                    assetBundleName = AssetDatabase.GetImplicitAssetBundleName(textureAssetPath);
                }

                var temp = new TextureProp
                {
                    propertyName    = propertyName,
                    displayName     = displayName,
                    texture         = texture,
                    assetBundleName = assetBundleName,
                    //dimension = ShaderUtil.GetTexDim(shader, i)
                };
                m_Properties.Add(temp);
            }
        }
Exemplo n.º 2
0
    private static Dictionary <string, List <Shader> > RetrieveAllBundles()
    {
        if (!Directory.Exists(shaderDirectory))
        {
            Debug.LogError("The shaders directory (\"" + shaderDirectory + "\") does not exist. Thus, there are no bundles to build. Aborting.");
            return(null);
        }

        // Get all assets
        string[] assets = Directory.GetFiles(shaderDirectory, "*.shader");
        Dictionary <string, List <Shader> > bundles = new Dictionary <string, List <Shader> >();

        // Get asset bundle names from each file
        foreach (string file in assets)
        {
            ShaderImporter importer = (ShaderImporter)AssetImporter.GetAtPath(file);

            if (importer == null)
            {
                Debug.LogWarning("Could not import asset \"" + file + "\". Skipping.");
                continue;
            }

            // Get asset bundle name
            string bundleName = importer.assetBundleName;
            if (bundleName != "")
            {
                // Create a folder for each bundle if applicable
                if (!Directory.Exists(assetBundleDirectory + "/" + bundleName))
                {
                    Directory.CreateDirectory(assetBundleDirectory + "/" + bundleName);
                }

                // Create a bundle if applicable
                if (!bundles.ContainsKey(bundleName))
                {
                    List <Shader> list = new List <Shader>();
                    bundles[bundleName] = list;
                }
                bundles[bundleName].Add(importer.GetShader());
            }
        }

        if (bundles.Count == 0)
        {
            Debug.LogError("There are no AssetBundles to build. Aborting.");
            return(null);
        }

        return(bundles);
    }
Exemplo n.º 3
0
        private void UpdateShaderMap()
        {
            int shadersWithDefaultMapCount = 0;

            m_shadersWithDefaultMap.Clear();

            StringBuilder report = new StringBuilder();

            string[] assetGUIDs = AssetDatabase.FindAssets("t:shader");
            Debug.Log("Found shaders: " + assetGUIDs.Length);
            foreach (var assetGUID in assetGUIDs)
            {
                var            assetPath      = AssetDatabase.GUIDToAssetPath(assetGUID);
                var            importer       = AssetImporter.GetAtPath(assetPath);
                ShaderImporter shaderImporter = importer as ShaderImporter;

                List <TextureProp> properties = new List <TextureProp>();
                GetProperties(ref properties, shaderImporter);
                StringBuilder sb = new StringBuilder();
                sb.AppendLine("Shader: " + assetPath + " (bundle: " + importer.assetBundleName + ")");
                bool hasDefaultMap = false;

                ShaderDefaultMap sdm = new ShaderDefaultMap();
                sdm.shader          = shaderImporter.GetShader();
                sdm.assetBundleName = importer.assetBundleName;
                sdm.properties      = new List <TextureProp>();

                // Walk through shader's textures and see if it's in the same asset bundle
                for (int i = 0; i < properties.Count; ++i)
                {
                    var prop = properties[i];
                    if (prop.texture != null)
                    {
                        sdm.properties.Add(prop);
                        hasDefaultMap = true;
                        sb.AppendLine("\t" + prop.propertyName + ", " + prop.texture.name + " (bundle: " + prop.assetBundleName + ")");
                    }
                }

                if (hasDefaultMap)
                {
                    m_shadersWithDefaultMap.Add(sdm);
                    report.AppendLine(sb.ToString());
                    ++shadersWithDefaultMapCount;
                }
            }

            //Debug.Log(report.ToString());
            //Debug.LogWarning("Shaders with Default Map: " + shadersWithDefaultMapCount);
        }
Exemplo n.º 4
0
    void OnGUI()
    {
        sGUIEnabled = GUI.enabled;

        EditorGUILayout.BeginHorizontal();
        TCP2_GUI.HeaderBig("TOONY COLORS PRO 2 - SHADER GENERATOR");
        TCP2_GUI.HelpButton("Shader Generator");
        EditorGUILayout.EndHorizontal();
        TCP2_GUI.Separator();

        var lW = EditorGUIUtility.labelWidth;

        EditorGUIUtility.labelWidth = 105f;

        //Avoid refreshing Template meta at every Repaint
        EditorGUILayout.BeginHorizontal();
        var _tmpTemplate = EditorGUILayout.ObjectField("Template:", Template.textAsset, typeof(TextAsset), false) as TextAsset;

        if (_tmpTemplate != Template.textAsset)
        {
            Template.SetTextAsset(_tmpTemplate);
        }
        //Load template
        if (loadTemplateMenu != null)
        {
            if (GUILayout.Button("Load ▼", EditorStyles.miniButton, GUILayout.ExpandWidth(false)))
            {
                loadTemplateMenu.ShowAsContext();
            }
        }

        /*
         * if(GUILayout.Button("Reload", EditorStyles.miniButton, GUILayout.ExpandWidth(false)))
         * {
         *      Template.Reload();
         * }
         */
        EditorGUILayout.EndHorizontal();

        //Template not found
        if (Template == null || Template.textAsset == null)
        {
            EditorGUILayout.HelpBox("Couldn't find template file!\n\nVerify that the file 'TCP2_ShaderTemplate_Default.txt' is in your project.\nPlease reimport the pack if you can't find it!", MessageType.Error);
            return;
        }

        //Infobox for custom templates
        if (!string.IsNullOrEmpty(Template.templateInfo))
        {
            TCP2_GUI.HelpBoxLayout(Template.templateInfo, MessageType.Info);
        }
        if (!string.IsNullOrEmpty(Template.templateWarning))
        {
            TCP2_GUI.HelpBoxLayout(Template.templateWarning, MessageType.Warning);
        }

        TCP2_GUI.Separator();

        //If current shader is unsaved, show yellow color
        var gColor = GUI.color;

        GUI.color = mDirtyConfig ? gColor * unsavedChangesColor : GUI.color;

        EditorGUI.BeginChangeCheck();
        mCurrentShader = EditorGUILayout.ObjectField("Current Shader:", mCurrentShader, typeof(Shader), false) as Shader;
        if (EditorGUI.EndChangeCheck())
        {
            if (mCurrentShader != null)
            {
                LoadCurrentConfigFromShader(mCurrentShader);
            }
        }
        EditorGUILayout.BeginHorizontal();

        GUILayout.Space(EditorGUIUtility.labelWidth + 4);
        if (mDirtyConfig)
        {
            var guiContent = new GUIContent("Unsaved changes");
            var rect       = GUILayoutUtility.GetRect(guiContent, EditorStyles.helpBox, GUILayout.Height(16));
            rect.y -= 2;
            GUI.Label(rect, guiContent, EditorStyles.helpBox);
        }

        GUILayout.FlexibleSpace();
        using (new EditorGUI.DisabledScope(mCurrentShader == null))
        {
            if (GUILayout.Button("Copy", EditorStyles.miniButtonLeft, GUILayout.Width(60f), GUILayout.Height(16)))
            {
                CopyShader();
            }
        }
        if (GUILayout.Button("Load ▼", EditorStyles.miniButtonMid, GUILayout.Width(60f), GUILayout.Height(16)))
        {
            loadShadersMenu.ShowAsContext();
        }
        if (GUILayout.Button("New", EditorStyles.miniButtonRight, GUILayout.Width(60f), GUILayout.Height(16)))
        {
            NewShader();
        }
        GUILayout.Space(18);            //leave space to align with the Object Field box
        EditorGUILayout.EndHorizontal();
        GUI.color = gColor;

        if (mCurrentConfig == null)
        {
            NewShader();
        }

        if (mCurrentConfig.isModifiedExternally)
        {
            EditorGUILayout.HelpBox("It looks like this shader has been modified externally/manually. Updating it will overwrite the changes.", MessageType.Warning);
        }

        EditorGUIUtility.labelWidth = lW;

        //Name & Filename
        TCP2_GUI.Separator();
        GUI.enabled = (mCurrentShader == null);
        EditorGUI.BeginChangeCheck();
        mCurrentConfig.ShaderName = EditorGUILayout.TextField(new GUIContent("Shader Name", "Path will indicate how to find the Shader in Unity's drop-down list"), mCurrentConfig.ShaderName);
        mCurrentConfig.ShaderName = Regex.Replace(mCurrentConfig.ShaderName, @"[^a-zA-Z0-9 _!/]", "");
        if (EditorGUI.EndChangeCheck() && sAutoNames)
        {
            mCurrentConfig.AutoNames();
        }
        GUI.enabled &= !sAutoNames;
        EditorGUILayout.BeginHorizontal();
        mCurrentConfig.Filename = EditorGUILayout.TextField("File Name", mCurrentConfig.Filename);
        mCurrentConfig.Filename = Regex.Replace(mCurrentConfig.Filename, @"[^a-zA-Z0-9 _!/]", "");
        GUILayout.Label(".shader", GUILayout.Width(50f));
        EditorGUILayout.EndHorizontal();
        GUI.enabled = sGUIEnabled;

        TCP2_GUI.Separator();

        //########################################################################################################
        // FEATURES

        TCP2_GUI.Header("FEATURES");

        //Scroll view
        mScrollPosition = EditorGUILayout.BeginScrollView(mScrollPosition);
        EditorGUI.BeginChangeCheck();

        if (Template.newSystem)
        {
            //New UI embedded into Template
            Template.FeaturesGUI(mCurrentConfig);

            if (mFirstHashPass)
            {
                mCurrentHash   = mCurrentConfig.ToHash();
                mFirstHashPass = false;
            }
        }
        else
        {
            EditorGUILayout.HelpBox("Old template versions aren't supported anymore.", MessageType.Warning);
        }

#if DEBUG_MODE
        TCP2_GUI.SeparatorBig();

        TCP2_GUI.SubHeaderGray("DEBUG MODE");

        GUILayout.BeginHorizontal();
        mDebugText = EditorGUILayout.TextField("Custom", mDebugText);
        if (GUILayout.Button("Add Feature", EditorStyles.miniButtonLeft, GUILayout.Width(80f)))
        {
            mCurrentConfig.Features.Add(mDebugText);
        }
        if (GUILayout.Button("Add Flag", EditorStyles.miniButtonRight, GUILayout.Width(80f)))
        {
            mCurrentConfig.Flags.Add(mDebugText);
        }

        GUILayout.EndHorizontal();
        GUILayout.Label("Features:");
        GUILayout.BeginHorizontal();
        int count = 0;
        for (int i = 0; i < mCurrentConfig.Features.Count; i++)
        {
            if (count >= 3)
            {
                count = 0;
                GUILayout.EndHorizontal();
                GUILayout.BeginHorizontal();
            }
            count++;
            if (GUILayout.Button(mCurrentConfig.Features[i], EditorStyles.toolbarButton, GUILayout.ExpandWidth(false)))
            {
                mCurrentConfig.Features.RemoveAt(i);
                break;
            }
        }
        GUILayout.EndHorizontal();
        GUILayout.Label("Flags:");
        GUILayout.BeginHorizontal();
        count = 0;
        for (int i = 0; i < mCurrentConfig.Flags.Count; i++)
        {
            if (count >= 3)
            {
                count = 0;
                GUILayout.EndHorizontal();
                GUILayout.BeginHorizontal();
            }
            count++;
            if (GUILayout.Button(mCurrentConfig.Flags[i], EditorStyles.toolbarButton, GUILayout.ExpandWidth(false)))
            {
                mCurrentConfig.Flags.RemoveAt(i);
                break;
            }
        }
        GUILayout.EndHorizontal();
        GUILayout.Label("Keywords:");
        GUILayout.BeginHorizontal();
        count = 0;
        foreach (KeyValuePair <string, string> kvp in mCurrentConfig.Keywords)
        {
            if (count >= 3)
            {
                count = 0;
                GUILayout.EndHorizontal();
                GUILayout.BeginHorizontal();
            }
            count++;
            if (GUILayout.Button(kvp.Key + ":" + kvp.Value, EditorStyles.toolbarButton, GUILayout.ExpandWidth(false)))
            {
                mCurrentConfig.Keywords.Remove(kvp.Key);
                break;
            }
        }
        GUILayout.EndHorizontal();

        //----------------------------------------------------------------

        Space();
        if (mCurrentShader != null)
        {
            if (mCurrentShaderImporter == null)
            {
                mCurrentShaderImporter = ShaderImporter.GetAtPath(AssetDatabase.GetAssetPath(mCurrentShader)) as ShaderImporter;
            }

            if (mCurrentShaderImporter != null && mCurrentShaderImporter.GetShader() == mCurrentShader)
            {
                mDebugExpandUserData = EditorGUILayout.Foldout(mDebugExpandUserData, "Shader UserData");
                if (mDebugExpandUserData)
                {
                    string[] userData = mCurrentShaderImporter.userData.Split(',');
                    foreach (var str in userData)
                    {
                        GUILayout.Label(str);
                    }
                }
            }
        }
#endif

        //Update config
        if (EditorGUI.EndChangeCheck())
        {
            var newHash = mCurrentConfig.ToHash();
            if (newHash != mCurrentHash)
            {
                mDirtyConfig = true;
            }
            else
            {
                mDirtyConfig = false;
            }
        }

        //Scroll view
        EditorGUILayout.EndScrollView();

        Space();

        //GENERATE

        EditorGUILayout.BeginHorizontal();
        GUILayout.FlexibleSpace();
        GUI.color = mDirtyConfig ? gColor * unsavedChangesColor : GUI.color;
        if (GUILayout.Button(mCurrentShader == null ? "Generate Shader" : "Update Shader", GUILayout.Width(120f), GUILayout.Height(30f)))
        {
            if (Template == null)
            {
                EditorUtility.DisplayDialog("TCP2 : Shader Generation", "Can't generate shader: no Template file defined!\n\nYou most likely want to link the TCP2_User.txt file to the Template field in the Shader Generator.", "Ok");
                return;
            }

            //Set config type
            if (Template.templateType != null)
            {
                mCurrentConfig.configType = Template.templateType;
            }

            //Set config file
            mCurrentConfig.templateFile = Template.textAsset.name;

            var generatedShader = TCP2_ShaderGeneratorUtils.Compile(mCurrentConfig, mCurrentShader, Template, true, !sOverwriteConfigs);
            ReloadUserShaders();
            if (generatedShader != null)
            {
                mDirtyConfig = false;
                LoadCurrentConfigFromShader(generatedShader);
            }

            //Workaround to force the inspector to refresh, so that state is reset.
            //Needed in case of switching between specular/metallic and related
            //options, while the inspector is opened, so that it shows/hides the
            //relevant properties according to the changes.
            TCP2_MaterialInspector_SurfacePBS_SG.InspectorNeedsUpdate = true;
        }
        GUI.color = gColor;
        EditorGUILayout.EndHorizontal();
        TCP2_GUI.Separator();

        // OPTIONS
        TCP2_GUI.Header("OPTIONS");

        GUILayout.BeginHorizontal();
        sSelectGeneratedShader = GUILayout.Toggle(sSelectGeneratedShader, new GUIContent("Select Generated Shader", "Will select the generated file in the Project view"), GUILayout.Width(180f));
        sAutoNames             = GUILayout.Toggle(sAutoNames, new GUIContent("Automatic Name", "Will automatically generate the shader filename based on its UI name"), GUILayout.ExpandWidth(false));
        GUILayout.EndHorizontal();
        GUILayout.BeginHorizontal();
        sOverwriteConfigs = GUILayout.Toggle(sOverwriteConfigs, new GUIContent("Always overwrite shaders", "Overwrite shaders when generating/updating (no prompt)"), GUILayout.Width(180f));
        sHideDisabled     = GUILayout.Toggle(sHideDisabled, new GUIContent("Hide disabled fields", "Hide properties settings when they cannot be accessed"), GUILayout.ExpandWidth(false));
        GUILayout.EndHorizontal();

        EditorGUILayout.BeginHorizontal();
        EditorGUI.BeginChangeCheck();
        TCP2_ShaderGeneratorUtils.CustomOutputDir = GUILayout.Toggle(TCP2_ShaderGeneratorUtils.CustomOutputDir, new GUIContent("Custom Output Directory:", "Will save the generated shaders in a custom directory within the Project"), GUILayout.Width(165f));
        GUI.enabled &= TCP2_ShaderGeneratorUtils.CustomOutputDir;
        if (TCP2_ShaderGeneratorUtils.CustomOutputDir)
        {
            TCP2_ShaderGeneratorUtils.OutputPath = EditorGUILayout.TextField("", TCP2_ShaderGeneratorUtils.OutputPath);
            if (GUILayout.Button("Select...", EditorStyles.miniButton, GUILayout.ExpandWidth(false)))
            {
                var outputPath = TCP2_Utils.OpenFolderPanel_ProjectPath("Choose custom output directory for TCP2 generated shaders");
                if (!string.IsNullOrEmpty(outputPath))
                {
                    TCP2_ShaderGeneratorUtils.OutputPath = outputPath;
                }
            }
        }
        else
        {
            EditorGUILayout.TextField("", TCP2_ShaderGeneratorUtils.OUTPUT_PATH);
        }
        if (EditorGUI.EndChangeCheck())
        {
            ReloadUserShaders();
        }

        GUI.enabled = sGUIEnabled;
        EditorGUILayout.EndHorizontal();

        EditorGUI.BeginChangeCheck();
        sLoadAllShaders = GUILayout.Toggle(sLoadAllShaders, new GUIContent("Reload Shaders from all Project", "Load shaders from all your Project folders instead of just Toony Colors Pro 2.\nEnable it if you move your generated shader files outside of the default TCP2 Generated folder."), GUILayout.ExpandWidth(false));
        if (EditorGUI.EndChangeCheck())
        {
            ReloadUserShaders();
        }

        TCP2_ShaderGeneratorUtils.SelectGeneratedShader = sSelectGeneratedShader;
    }
Exemplo n.º 5
0
    void OnGUI()
    {
        sGUIEnabled = GUI.enabled;

        EditorGUILayout.BeginHorizontal();
        TCP2_GUI.HeaderBig("TOONY COLORS PRO 2 - SHADER GENERATOR");
        TCP2_GUI.HelpButton("Shader Generator");
        EditorGUILayout.EndHorizontal();
        TCP2_GUI.Separator();

        float lW = EditorGUIUtility.labelWidth;

        EditorGUIUtility.labelWidth = 105f;

        //Avoid refreshing Template meta at every Repaint
        EditorGUILayout.BeginHorizontal();
        TextAsset _tmpTemplate = EditorGUILayout.ObjectField("Template:", Template.textAsset, typeof(TextAsset), false) as TextAsset;

        if (_tmpTemplate != Template.textAsset)
        {
            Template.SetTextAsset(_tmpTemplate);
        }
        //Load template
        if (loadTemplateMenu != null)
        {
            if (GUILayout.Button("Load ▼", EditorStyles.miniButton, GUILayout.ExpandWidth(false)))
            {
                loadTemplateMenu.ShowAsContext();
            }
        }
        EditorGUILayout.EndHorizontal();

        //Template not found
        if (Template == null || Template.textAsset == null)
        {
            EditorGUILayout.HelpBox("Couldn't find template file!\n\nVerify that the file 'TCP2_ShaderTemplate_Default.txt' is in your project.\nPlease reimport the pack if you can't find it!", MessageType.Error);
            return;
        }

        //Infobox for custom templates
        if (!string.IsNullOrEmpty(Template.templateInfo))
        {
            EditorGUILayout.HelpBox(Template.templateInfo, MessageType.Info);
        }
        if (!string.IsNullOrEmpty(Template.templateWarning))
        {
            EditorGUILayout.HelpBox(Template.templateWarning, MessageType.Warning);
        }

        TCP2_GUI.Separator();

        EditorGUI.BeginChangeCheck();
        EditorGUILayout.BeginHorizontal();
        mCurrentShader = EditorGUILayout.ObjectField("Current Shader:", mCurrentShader, typeof(Shader), false) as Shader;
        if (EditorGUI.EndChangeCheck())
        {
            if (mCurrentShader != null)
            {
                LoadCurrentConfigFromShader(mCurrentShader);
            }
        }
        if (GUILayout.Button("Copy Shader", EditorStyles.miniButton, GUILayout.Width(78f)))
        {
            CopyShader();
        }
        if (GUILayout.Button("New Shader", EditorStyles.miniButton, GUILayout.Width(76f)))
        {
            NewShader();
        }
        EditorGUILayout.EndHorizontal();

        if (mCurrentConfig.isModifiedExternally)
        {
            EditorGUILayout.HelpBox("It looks like this shader has been modified externally/manually. Updating it will overwrite the changes.", MessageType.Warning);
        }

        if (mUserShaders != null && mUserShaders.Length > 0)
        {
            EditorGUI.BeginChangeCheck();
            int   prevChoice = mConfigChoice;
            Color gColor     = GUI.color;
            GUI.color = mDirtyConfig ? gColor * Color.yellow : GUI.color;
            GUILayout.BeginHorizontal();
            mConfigChoice = EditorGUILayout.Popup("Load Shader:", mConfigChoice, mUserShadersLabels.ToArray());
            if (GUILayout.Button("◄", EditorStyles.miniButtonLeft, GUILayout.Width(22)))
            {
                mConfigChoice--;
                if (mConfigChoice < 1)
                {
                    mConfigChoice = mUserShaders.Length;
                }
            }
            if (GUILayout.Button("►", EditorStyles.miniButtonRight, GUILayout.Width(22)))
            {
                mConfigChoice++;
                if (mConfigChoice > mUserShaders.Length)
                {
                    mConfigChoice = 1;
                }
            }
            GUILayout.EndHorizontal();
            GUI.color = gColor;
            if (EditorGUI.EndChangeCheck() && prevChoice != mConfigChoice)
            {
                bool load = true;
                if (mDirtyConfig)
                {
                    if (mCurrentShader != null)
                    {
                        load = EditorUtility.DisplayDialog("TCP2 : Shader Generation", "You have unsaved changes for the following shader:\n\n" + mCurrentShader.name + "\n\nDiscard the changes and load a new shader?", "Yes", "No");
                    }
                    else
                    {
                        load = EditorUtility.DisplayDialog("TCP2 : Shader Generation", "You have unsaved changes.\n\nDiscard the changes and load a new shader?", "Yes", "No");
                    }
                }

                if (load)
                {
                    //New Shader
                    if (mConfigChoice == 0)
                    {
                        NewShader();
                    }
                    else
                    {
                        //Load selected Shader
                        Shader selectedShader = mUserShaders[mConfigChoice - 1];
                        mCurrentShader = selectedShader;
                        LoadCurrentConfigFromShader(mCurrentShader);
                    }
                }
                else
                {
                    //Revert choice
                    mConfigChoice = prevChoice;
                }
            }
        }

        EditorGUIUtility.labelWidth = lW;

        if (mCurrentConfig == null)
        {
            NewShader();
        }

        //Name & Filename
        TCP2_GUI.Separator();
        GUI.enabled = (mCurrentShader == null);
        EditorGUI.BeginChangeCheck();
        mCurrentConfig.ShaderName = EditorGUILayout.TextField(new GUIContent("Shader Name", "Path will indicate how to find the Shader in Unity's drop-down list"), mCurrentConfig.ShaderName);
        mCurrentConfig.ShaderName = Regex.Replace(mCurrentConfig.ShaderName, @"[^a-zA-Z0-9 _!/]", "");
        if (EditorGUI.EndChangeCheck() && sAutoNames)
        {
            mCurrentConfig.AutoNames();
        }
        GUI.enabled &= !sAutoNames;
        EditorGUILayout.BeginHorizontal();
        mCurrentConfig.Filename = EditorGUILayout.TextField("File Name", mCurrentConfig.Filename);
        mCurrentConfig.Filename = Regex.Replace(mCurrentConfig.Filename, @"[^a-zA-Z0-9 _!/]", "");
        GUILayout.Label(".shader", GUILayout.Width(50f));
        EditorGUILayout.EndHorizontal();
        GUI.enabled = sGUIEnabled;

        Space();

        //########################################################################################################
        // FEATURES

        TCP2_GUI.Header("FEATURES");

        //Scroll view
        mScrollPosition = EditorGUILayout.BeginScrollView(mScrollPosition);
        EditorGUI.BeginChangeCheck();

        if (Template.newSystem)
        {
            //New UI embedded into Template
            Template.FeaturesGUI(mCurrentConfig);
        }
        else
        {
            EditorGUILayout.HelpBox("Old template versions aren't supported anymore.", MessageType.Warning);
        }

#if DEBUG_MODE
        TCP2_GUI.SeparatorBig();

        TCP2_GUI.SubHeaderGray("DEBUG MODE");

        GUILayout.BeginHorizontal();
        mDebugText = EditorGUILayout.TextField("Custom", mDebugText);
        if (GUILayout.Button("Add Feature", EditorStyles.miniButtonLeft, GUILayout.Width(80f)))
        {
            mCurrentConfig.Features.Add(mDebugText);
        }
        if (GUILayout.Button("Add Flag", EditorStyles.miniButtonRight, GUILayout.Width(80f)))
        {
            mCurrentConfig.Flags.Add(mDebugText);
        }

        GUILayout.EndHorizontal();
        GUILayout.Label("Features:");
        GUILayout.BeginHorizontal();
        int count = 0;
        for (int i = 0; i < mCurrentConfig.Features.Count; i++)
        {
            if (count >= 3)
            {
                count = 0;
                GUILayout.EndHorizontal();
                GUILayout.BeginHorizontal();
            }
            count++;
            if (GUILayout.Button(mCurrentConfig.Features[i], EditorStyles.toolbarButton, GUILayout.ExpandWidth(false)))
            {
                mCurrentConfig.Features.RemoveAt(i);
                break;
            }
        }
        GUILayout.EndHorizontal();
        GUILayout.Label("Flags:");
        GUILayout.BeginHorizontal();
        count = 0;
        for (int i = 0; i < mCurrentConfig.Flags.Count; i++)
        {
            if (count >= 3)
            {
                count = 0;
                GUILayout.EndHorizontal();
                GUILayout.BeginHorizontal();
            }
            count++;
            if (GUILayout.Button(mCurrentConfig.Flags[i], EditorStyles.toolbarButton, GUILayout.ExpandWidth(false)))
            {
                mCurrentConfig.Flags.RemoveAt(i);
                break;
            }
        }
        GUILayout.EndHorizontal();
        GUILayout.Label("Keywords:");
        GUILayout.BeginHorizontal();
        count = 0;
        foreach (KeyValuePair <string, string> kvp in mCurrentConfig.Keywords)
        {
            if (count >= 3)
            {
                count = 0;
                GUILayout.EndHorizontal();
                GUILayout.BeginHorizontal();
            }
            count++;
            if (GUILayout.Button(kvp.Key + ":" + kvp.Value, EditorStyles.toolbarButton, GUILayout.ExpandWidth(false)))
            {
                mCurrentConfig.Keywords.Remove(kvp.Key);
                break;
            }
        }
        GUILayout.EndHorizontal();

        //----------------------------------------------------------------

        Space();
        if (mCurrentShader != null)
        {
            if (mCurrentShaderImporter == null)
            {
                mCurrentShaderImporter = ShaderImporter.GetAtPath(AssetDatabase.GetAssetPath(mCurrentShader)) as ShaderImporter;
            }

            if (mCurrentShaderImporter != null && mCurrentShaderImporter.GetShader() == mCurrentShader)
            {
                mDebugExpandUserData = EditorGUILayout.Foldout(mDebugExpandUserData, "Shader UserData");
                if (mDebugExpandUserData)
                {
                    string[] userData = mCurrentShaderImporter.userData.Split(',');
                    foreach (var str in userData)
                    {
                        GUILayout.Label(str);
                    }
                }
            }
        }
#endif

        //Update config
        if (EditorGUI.EndChangeCheck())
        {
            int newHash = mCurrentConfig.ToHash();
            if (newHash != mCurrentHash)
            {
                mDirtyConfig = true;
            }
            else
            {
                mDirtyConfig = false;
            }
        }

        //Scroll view
        EditorGUILayout.EndScrollView();

        Space();

        //GENERATE

        EditorGUILayout.BeginHorizontal();
        GUILayout.FlexibleSpace();
        if (GUILayout.Button(mCurrentShader == null ? "Generate Shader" : "Update Shader", GUILayout.Width(120f), GUILayout.Height(30f)))
        {
            if (Template == null)
            {
                EditorUtility.DisplayDialog("TCP2 : Shader Generation", "Can't generate shader: no Template file defined!\n\nYou most likely want to link the TCP2_User.txt file to the Template field in the Shader Generator.", "Ok");
                return;
            }

            //Set config type
            if (Template.templateType != null)
            {
                mCurrentConfig.configType = Template.templateType;
            }

            //Set config file
            mCurrentConfig.templateFile = Template.textAsset.name;

            Shader generatedShader = TCP2_ShaderGeneratorUtils.Compile(mCurrentConfig, mCurrentShader, Template, true, !sOverwriteConfigs);
            ReloadUserShaders();
            if (generatedShader != null)
            {
                mDirtyConfig = false;
                LoadCurrentConfigFromShader(generatedShader);
            }
        }
        EditorGUILayout.EndHorizontal();
        TCP2_GUI.Separator();

        // OPTIONS
        TCP2_GUI.Header("OPTIONS");

        GUILayout.BeginHorizontal();
        sSelectGeneratedShader = GUILayout.Toggle(sSelectGeneratedShader, new GUIContent("Select Generated Shader", "Will select the generated file in the Project view"), GUILayout.Width(180f));
        sAutoNames             = GUILayout.Toggle(sAutoNames, new GUIContent("Automatic Name", "Will automatically generate the shader filename based on its UI name"), GUILayout.ExpandWidth(false));
        GUILayout.EndHorizontal();
        GUILayout.BeginHorizontal();
        sOverwriteConfigs = GUILayout.Toggle(sOverwriteConfigs, new GUIContent("Always overwrite shaders", "Overwrite shaders when generating/updating (no prompt)"), GUILayout.Width(180f));
        sHideDisabled     = GUILayout.Toggle(sHideDisabled, new GUIContent("Hide disabled fields", "Hide properties settings when they cannot be accessed"), GUILayout.ExpandWidth(false));
        GUILayout.EndHorizontal();

        EditorGUILayout.BeginHorizontal();
        EditorGUI.BeginChangeCheck();
        TCP2_ShaderGeneratorUtils.CustomOutputDir = GUILayout.Toggle(TCP2_ShaderGeneratorUtils.CustomOutputDir, new GUIContent("Custom Output Directory:", "Will save the generated shaders in a custom directory within the Project"), GUILayout.Width(165f));
        GUI.enabled &= TCP2_ShaderGeneratorUtils.CustomOutputDir;
        if (TCP2_ShaderGeneratorUtils.CustomOutputDir)
        {
            TCP2_ShaderGeneratorUtils.OutputPath = EditorGUILayout.TextField("", TCP2_ShaderGeneratorUtils.OutputPath);
            if (GUILayout.Button("Select...", EditorStyles.miniButton, GUILayout.ExpandWidth(false)))
            {
                string path = EditorUtility.OpenFolderPanel("Choose custom output directory for TCP2 generated shaders", Application.dataPath, "");
                if (!string.IsNullOrEmpty(path))
                {
                    bool validPath = TCP2_Utils.SystemToUnityPath(ref path);
                    if (validPath)
                    {
                        if (path == "Assets")
                        {
                            TCP2_ShaderGeneratorUtils.OutputPath = "/";
                        }
                        else
                        {
                            TCP2_ShaderGeneratorUtils.OutputPath = path.Substring("Assets/".Length);
                        }
                    }
                    else
                    {
                        EditorApplication.Beep();
                        EditorUtility.DisplayDialog("Invalid Path", "The selected path is invalid.\n\nPlease select a folder inside the \"Assets\" folder of your project!", "Ok");
                    }
                }
            }
        }
        else
        {
            EditorGUILayout.TextField("", TCP2_ShaderGeneratorUtils.OUTPUT_PATH);
        }
        if (EditorGUI.EndChangeCheck())
        {
            ReloadUserShaders();
        }

        GUI.enabled = sGUIEnabled;
        EditorGUILayout.EndHorizontal();

        EditorGUI.BeginChangeCheck();
        sLoadAllShaders = GUILayout.Toggle(sLoadAllShaders, new GUIContent("Reload Shaders from all Project", "Load shaders from all your Project folders instead of just Toony Colors Pro 2.\nEnable it if you move your generated shader files outside of the default TCP2 Generated folder."), GUILayout.ExpandWidth(false));
        if (EditorGUI.EndChangeCheck())
        {
            ReloadUserShaders();
        }

        TCP2_ShaderGeneratorUtils.SelectGeneratedShader = sSelectGeneratedShader;
    }