Folder description

I know this is such a small feature that would be added. But I think it makes a huge difference in big projects or third party ones. Instead of having text files spread across your project Assets, blending them into folder could show useful if the contents of the folder end up being more complex to explain than a title length string (the folder name, that has its limitation).

This is totally doable using an editor script! This was a fun little problem to solve:

using System.IO;
using UnityEditor;
using UnityEngine;

[CustomEditor( typeof( DefaultAsset ), isFallback = true )]
public class FolderDescription : Editor {
   static class Styles {
       public static readonly GUIStyle invisibleTextEditor = new GUIStyle( "TextArea" );

       static Styles () {
           var label = EditorStyles.label;
           invisibleTextEditor.normal = new GUIStyleState();
           invisibleTextEditor.normal.background = label.normal.background;
           invisibleTextEditor.normal.textColor = label.normal.textColor;
       }
   }

   const string descriptionFilename = ".desc";

   bool isFolder;
   string description;

   private void OnEnable () {
       var path = AssetDatabase.GetAssetPath( target );

       if( Directory.Exists( path ) ) {
           isFolder = true;
           var descriptionPath = Path.Combine( path, descriptionFilename );

           try {
               description = File.ReadAllText( descriptionPath );
           } catch( IOException ) {}
       }
   }

   public override void OnInspectorGUI () {
       if( isFolder ) {
           GUI.enabled = true;
           EditorGUI.BeginChangeCheck();

           var descriptionContent = new GUIContent( description );

           description = EditorGUILayout.TextArea( description, Styles.invisibleTextEditor, GUILayout.ExpandHeight( false ), GUILayout.ExpandWidth( true ) );

           if( string.IsNullOrEmpty( description ) && GUIUtility.keyboardControl == 0 ) {
               var rect = GUILayoutUtility.GetLastRect();

               GUI.Label( rect, "Click to add a folder description", EditorStyles.centeredGreyMiniLabel );
           }

           if( EditorGUI.EndChangeCheck() ) {
               // we get the asset path again instead of caching it in case the folder has moved since OnEnable was called
               var descriptionPath = Path.Combine( AssetDatabase.GetAssetPath( target ), descriptionFilename );

               if( !string.IsNullOrEmpty( description ) ) {
                   try {
                       File.SetAttributes( descriptionPath, FileAttributes.Normal );
                   } catch( IOException ) {}

                   File.WriteAllText( descriptionPath, description );

                   try {
                       File.SetAttributes( descriptionPath, FileAttributes.Hidden );
                   } catch( IOException ) {}
               } else {
                   try {
                       File.Delete( descriptionPath );
                   } catch( IOException ) {}
               }
           }
       } else {
           DrawDefaultInspector();
       }
   }
}

It works by creating a hidden file inside the given folder that contains a description for that folder. The file is also hidden from Unity so it isn’t imported. If you want visible description files, just strip out the code that sets the file attributes to be hidden. If you want the files to be visible in Unity for whatever reason, rename them to something that doesn’t start with a dot.

1 Like

This is actually incredible, I didn’t know you could change folders. But sadly it’s not working for me, and I can’t find any errors in the code. My unity version is 2019.2.9f1 Personal

The script was built for 2018. It seems that there are some incompatibilities between these two versions.

EDIT: Here’s a version that supports 2019. For whatever reason OnInspectorGUI is not called for folders in 2019, so the script now draws the GUI in OnHeaderGUI instead for 2019+:

using System.IO;
using UnityEditor;
using UnityEngine;

[CustomEditor( typeof( DefaultAsset ), isFallback = true )]
public class FolderDescription : Editor {
    static class Styles {
        public static readonly GUIStyle invisibleTextEditor = new GUIStyle( "TextArea" );

        static Styles () {
            var label = EditorStyles.label;
            invisibleTextEditor.normal = new GUIStyleState();
            invisibleTextEditor.normal.background = label.normal.background;
            invisibleTextEditor.normal.textColor = label.normal.textColor;
        }
    }

    const string descriptionFilename = ".desc";

    bool isFolder;
    string description;

    private void OnEnable () {
        var path = AssetDatabase.GetAssetPath( target );

        if( Directory.Exists( path ) ) {
            isFolder = true;
            var descriptionPath = Path.Combine( path, descriptionFilename );

            try {
                description = File.ReadAllText( descriptionPath );
            } catch( IOException ) {}
        }
    }

#if UNITY_2019_1_OR_NEWER
    protected override void OnHeaderGUI () {
        base.OnHeaderGUI();

        if( isFolder ) {
            DoDescriptionEditor();
        }
    }
#else
    public override void OnInspectorGUI () {
        if( isFolder ) {
            DoDescriptionEditor();
        } else {
            DrawDefaultInspector();
        }
    }
#endif

    private void DoDescriptionEditor () {
        GUI.enabled = true;
        EditorGUI.BeginChangeCheck();

        var descriptionContent = new GUIContent( description );

        description = EditorGUILayout.TextArea( description, Styles.invisibleTextEditor, GUILayout.ExpandHeight( false ), GUILayout.ExpandWidth( true ) );

        if( string.IsNullOrEmpty( description ) && GUIUtility.keyboardControl == 0 ) {
            var rect = GUILayoutUtility.GetLastRect();

            GUI.Label( rect, "Click to add a folder description", EditorStyles.centeredGreyMiniLabel );
        }

        if( EditorGUI.EndChangeCheck() ) {
            // we get the asset path again instead of caching it in case the folder has moved since OnEnable was called
            var descriptionPath = Path.Combine( AssetDatabase.GetAssetPath( target ), descriptionFilename );

            if( !string.IsNullOrEmpty( description ) ) {
                try {
                    File.SetAttributes( descriptionPath, FileAttributes.Normal );
                } catch( IOException ) { }

                File.WriteAllText( descriptionPath, description );

                try {
                    File.SetAttributes( descriptionPath, FileAttributes.Hidden );
                } catch( IOException ) { }
            } else {
                try {
                    File.Delete( descriptionPath );
                } catch( IOException ) { }
            }
        }
    }
}
2 Likes

@Madgvox , I can’t take you enough, my team has been able to organize so much better with such addition! This should be native.

Here is an up-to-date version of the above script, working for 2020.1.1.

  • Added title label
  • Removed styling, to default GUI style
  • Added debug log for catch exceptions
  • Draws an additional 16 units in case you have Addressable Assets installed. The checkbox covers the description text area otherwise.

Unity Please make this a stock feature!

using System.IO;
using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(DefaultAsset), isFallback = true)]
public class FolderDescription : Editor
{
    // Thanks for all the fish!
    const string descriptionFilename = ".desc";

    bool isFolder;
    string description;

    private void OnEnable()
    {
        var path = AssetDatabase.GetAssetPath(target);

        if (Directory.Exists(path))
        {
            isFolder = true;
            var descriptionPath = Path.Combine(path, descriptionFilename);

            try
            {
                description = File.ReadAllText(descriptionPath);
            }
            catch (System.Exception ea) { Error(ea); } // Print to debug error message only if there was an advanced error
        }
    }

#if UNITY_2019_1_OR_NEWER
    protected override void OnHeaderGUI()
    {
        base.OnHeaderGUI();

        if (isFolder)
        {
            DoDescriptionEditor();
        }
    }
#endif

    private void DoDescriptionEditor()
    {
        GUI.enabled = true;
        EditorGUI.BeginChangeCheck();

        var descriptionContent = new GUIContent(description);

        GUILayout.Label("Description");
        EditorGUILayout.Space(8);

        description = EditorGUILayout.TextArea(description, GUILayout.ExpandHeight(false), GUILayout.ExpandWidth(true)); // Reverted styling to default

        EditorGUILayout.Space(16);

        if (EditorGUI.EndChangeCheck())
        {
            // we get the asset path again instead of caching it in case the folder has moved since OnEnable was called
            var descriptionPath = Path.Combine(AssetDatabase.GetAssetPath(target), descriptionFilename);

            if (!string.IsNullOrEmpty(description))
            {
                try
                {
                    File.SetAttributes(descriptionPath, FileAttributes.Normal);
                }
                catch (System.Exception ea) { Error(ea); } // Print to debug error message only if there was an advanced error

                File.WriteAllText(descriptionPath, description);

                try
                {
                    File.SetAttributes(descriptionPath, FileAttributes.Normal);
                }
                catch (System.Exception ea) { Error(ea); } // Print to debug error message only if there was an advanced error
            }
            else
            {
                try
                {
                    File.Delete(descriptionPath);
                }
                catch (System.Exception ea) { Error(ea); } // Print to debug error message only if there was an advanced error
            }
        }
    }

    void Error(System.Exception ea)
    {
        if (ea is System.IO.FileNotFoundException)
        {
            return;
        }
        Debug.LogError(ea.ToString());
    }
}
2 Likes