SubAssetEditor icon indicating copy to clipboard operation
SubAssetEditor copied to clipboard

How should I scroll the list? Expanding or collapsing the list doesn't seem to work either.

Open Wumuuu opened this issue 1 year ago • 3 comments

My unity version is 2022.3.9f1, plugin version is 1.0.2 installed via UPM Git Extension. I can't scroll the list to see more content by scrolling the mouse wheel except by dragging the window size. I can't expand or collapse the list either but that's ok。 It would be nice to have a popup window warning when deleting sub-assets

Wumuuu avatar Feb 23 '24 18:02 Wumuuu

Get the same issue

builder-main avatar Jun 12 '24 18:06 builder-main

GUI.Toggle(rLabel, true, "<b>Sub Asset</b>", "IN Foldout"); The toggles are doing nothing in the code. We should have something like

 var myToggle = GUI.Toggle ...
if(!toggle) //exit display

builder-main avatar Jun 12 '24 18:06 builder-main

using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Linq;
using System.IO;

namespace Coffee.Editors
{
    internal class SubAssetEditor : EditorWindow
    {
        private static GUIContent _contentNoRef;
        private static GUIContent _contentAdd;
        private static GUIContent _contentDelete;
        private static GUIContent _contentImport;
        private static GUIContent _contentExport;
        private static bool _cached = false;
        const float ICON_SIZE = 20;

        private bool _isLocked;
        private bool _isRenaming;
        private bool _hasSelectionChanged;
        private Object _current;
        private Vector2 _scrollPosition;
        private List<Object> _subAssets = new List<Object>();
        private readonly List<Object> _referencingAssets = new List<Object>();
        private static SubAssetEditor _subassetEditor;

        private static void CacheGUI()
        {
            if (_cached)
                return;
            _cached = true;

            _contentNoRef = new GUIContent(EditorGUIUtility.FindTexture("console.warnicon.sml"), "Not referenced by main asset");
            _contentAdd = new GUIContent(EditorGUIUtility.FindTexture("toolbar plus"), "Add to sub assets");
            _contentDelete = new GUIContent(EditorGUIUtility.FindTexture("treeeditor.trash"), "Delete asset");
            _contentImport = new GUIContent("Drag & Drop object to add as sub-asset.", EditorGUIUtility.FindTexture("toolbar plus"));
            _contentExport = new GUIContent(EditorGUIUtility.FindTexture("saveactive"), "Export asset");
        }

        [MenuItem("Assets/Sub Asset Editor")]
        public static void OnOpenFromMenu()
        {
            _subassetEditor = EditorWindow.GetWindow<SubAssetEditor>("Sub Asset");
        }

        private void OnEnable()
        {
            Selection.selectionChanged += OnSelectionChanged;
        }

        private void OnDisable()
        {
            Selection.selectionChanged -= OnSelectionChanged;
        }

        private void OnSelectionChanged()
        {
            // On select new asset.
            var active = Selection.activeObject;
            if (!_isLocked && active && _current != active && !(active is SceneAsset) && AssetDatabase.IsMainAsset(active))
            {
                OnSelectionChanged(active);
            }
        }

        private void OnSelectionChanged(Object active)
        {
            _current = active;

            // Find sub-assets.
            var assetPath = AssetDatabase.GetAssetPath(active);
            _subAssets = AssetDatabase.LoadAllAssetsAtPath(assetPath)
                .Where(x => x != null && !(x is GameObject || x is Component) && x != _current && 0 == (x.hideFlags & HideFlags.HideInHierarchy))
                .Distinct()
                .ToList();

            // Find referencing assets.
            _referencingAssets.Clear();
            foreach (var o in AssetDatabase.LoadAllAssetsAtPath(AssetDatabase.GetAssetPath(active)))
            {
                if (o == null)
                {
                    continue;
                }

                var sp = new SerializedObject(o).GetIterator();
                sp.Next(true);

                // Search referencing asset in SerializedProperties.
                while (sp.Next(true))
                {
                    if (sp.propertyType != SerializedPropertyType.ObjectReference || !sp.objectReferenceValue) continue;

                    var asset = sp.objectReferenceValue;
                    if (!(asset is GameObject || asset is Component) && active != asset && o != asset && 0 == (asset.hideFlags & HideFlags.HideInHierarchy) && !_referencingAssets.Contains(asset))
                    {
                        _referencingAssets.Add(asset);
                    }
                }
            }

            // Refresh GUI.
            Repaint();
        }

        private void DeleteSubAsset(Object asset)
        {
            Object.DestroyImmediate(asset, true);
            _hasSelectionChanged = true;
        }

        private void AddSubAsset(Object asset)
        {
            AddSubAsset(new Object[] {asset});
        }

        /// <summary>
        /// Replace the specified obj, oldAsset and newAsset.
        /// </summary>
        private void AddSubAsset(IEnumerable<Object> assets)
        {
            // accepted object
            assets = assets
                .Where(x => x != _current && !(x is SceneAsset) && AssetDatabase.Contains(x) && !_subAssets.Contains(x));

            // Replace targets
            var replaceTargets = AssetDatabase.LoadAllAssetsAtPath(AssetDatabase.GetAssetPath(_current))
                .Select(x => new SerializedObject(x))
                .ToArray();

            //
            foreach (var asset in assets)
            {
                var newInstance = Object.Instantiate(asset);
                newInstance.name = asset.name;
                AssetDatabase.AddObjectToAsset(newInstance, _current);

                // Find referencing assets.
                foreach (var so in replaceTargets)
                {
                    var sp = so.GetIterator();

                    // Search referencing asset in SerializedProperties.
                    while (sp.Next(true))
                    {
                        // Replace to new object.
                        if (sp.propertyType == SerializedPropertyType.ObjectReference && sp.objectReferenceValue == asset)
                        {
                            sp.objectReferenceValue = newInstance;
                        }
                    }

                    sp.serializedObject.ApplyModifiedProperties();
                }
            }

            _hasSelectionChanged = true;
        }

        private void OnGUI()
        {
            // Start the ScrollView
            _scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
            
            CacheGUI();
            if (!_current) return;

            using (new EditorGUILayout.HorizontalScope())
            {
                var rLabel = EditorGUILayout.GetControlRect(GUILayout.Width(80));
                var toggle = GUI.Toggle(rLabel, true, "<b>Main Asset</b>", "IN Foldout");
                var rLock = EditorGUILayout.GetControlRect(GUILayout.Width(20));
                rLock.y += 2;
                if (GUI.Toggle(rLock, _isLocked, GUIContent.none, "IN LockButton") != _isLocked)
                {
                    _isLocked = !_isLocked;
                }

                GUILayout.FlexibleSpace();
            }

            EditorGUI.indentLevel++;
            using (new EditorGUILayout.HorizontalScope())
            {
                var r = EditorGUILayout.GetControlRect(true);

                r.width -= 20;
                EditorGUI.ObjectField(r, _current, _current.GetType(), false);

                r.x += r.width;
                r.width = 20;
                r.height = 20;
                if (GUI.Button(r, _contentDelete, EditorStyles.label))
                {
                    DeleteSubAsset(_current);
                }
            }

            EditorGUI.indentLevel--;

            GUILayout.Space(10);
            using (new EditorGUILayout.HorizontalScope())
            {
                var rLabel = EditorGUILayout.GetControlRect(GUILayout.Width(80));
                GUI.Toggle(rLabel, true, "<b>Sub Asset</b>", "IN Foldout");

                var rRename = EditorGUILayout.GetControlRect(GUILayout.Width(60));
                _isRenaming = GUI.Toggle(rRename, _isRenaming, "Rename", EditorStyles.miniButton);
                GUILayout.FlexibleSpace();
            }

            EditorGUI.indentLevel++;
            foreach (var asset in _subAssets)
            {
                var r = EditorGUILayout.GetControlRect(true);

                r.width -= 60;
                var rField = new Rect(r);
                if (_isRenaming)
                {
                    rField.width = 12;
                    rField.height = 12;
                    //Draw icon of current object.
                    EditorGUI.LabelField(r, new GUIContent(AssetPreview.GetMiniThumbnail(asset)));
                    EditorGUI.BeginChangeCheck();

                    rField.x += rField.width + 4;
                    rField.width = r.width - rField.width;
                    rField.height = r.height;
                    asset.name = EditorGUI.DelayedTextField(rField, asset.name);
                    if (EditorGUI.EndChangeCheck())
                    {
                        AssetDatabase.SaveAssets();
                    }
                }
                else
                {
                    EditorGUI.ObjectField(rField, asset, asset.GetType(), false);
                }

                r.x += r.width;
                r.width = 20;
                r.height = 20;
                if (!_referencingAssets.Contains(asset))
                {
                    GUI.Label(r, _contentNoRef);
                }

                r.x += r.width;
                if (GetFileExtension(asset).Length != 0 && GUI.Button(r, _contentExport, EditorStyles.label))
                {
                    ExportSubAsset(asset);
                }

                r.x += r.width;
                if (GUI.Button(r, _contentDelete, EditorStyles.label))
                {
                    DeleteSubAsset(asset);
                }
            }

            EditorGUI.indentLevel--;

            GUILayout.Space(10);
            GUILayout.Toggle(true, "<b>Referencing Objects</b>", "IN Foldout");
            EditorGUI.indentLevel++;
            EditorGUILayout.HelpBox("Sub assets are excluded.", MessageType.None);
            foreach (var asset in _referencingAssets.Except(_subAssets))
            {
                var r = EditorGUILayout.GetControlRect();

                r.width -= 20;
                EditorGUI.ObjectField(r, asset, asset.GetType(), false);

                r.x += r.width;
                r.y -= 1;
                r.width = 20;
                r.height = 20;

                // Add object to sub asset.
                if (GUI.Button(r, _contentAdd, EditorStyles.label))
                {
                    var addAsset = asset;
                    EditorApplication.delayCall += () => AddSubAsset(addAsset);
                }
            }

            EditorGUI.indentLevel--;

            DrawImportArea();
            EditorGUILayout.EndScrollView();

            if (!_hasSelectionChanged) return;

            _hasSelectionChanged = false;
            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
            OnSelectionChanged(_current);

            
        }

        /// <summary>
        /// Draws an import area where assets can be added by drag & drop.
        /// </summary>
        private void DrawImportArea()
        {
            GUILayout.Space(5);
            var dropArea = GUILayoutUtility.GetRect(0, 20, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true));
            GUI.Box(dropArea, _contentImport, EditorStyles.helpBox);

            var id = GUIUtility.GetControlID(FocusType.Passive);
            var evt = Event.current;
            switch (evt.type)
            {
                case EventType.DragUpdated:
                case EventType.DragPerform:
                    if (!dropArea.Contains(evt.mousePosition))
                        break;

                    DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
                    DragAndDrop.activeControlID = id;

                    if (evt.type == EventType.DragPerform)
                    {
                        DragAndDrop.AcceptDrag();
                        DragAndDrop.activeControlID = 0;

                        AddSubAsset(DragAndDrop.objectReferences);
                    }

                    Event.current.Use();
                    break;
            }
        }

        /// <summary>
        /// Export sub-asset.
        /// </summary>
        private void ExportSubAsset(Object obj)
        {
            var exportDir = Path.GetDirectoryName(AssetDatabase.GetAssetPath(_current));
            var exportName = obj.name + " (Exported)." + GetFileExtension(obj);
            var uniquePath = AssetDatabase.GenerateUniqueAssetPath(Path.Combine(exportDir, exportName));
            AssetDatabase.CreateAsset(Object.Instantiate(obj), uniquePath);
            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
        }

        private static string GetFileExtension(Object obj)
        {
            if (obj is AnimationClip)
                return "anim";
            else if (obj is UnityEditor.Animations.AnimatorController)
                return "controller";
            else if (obj is AnimatorOverrideController)
                return "overrideController";
            else if (obj is Material)
                return "mat";
            else if (obj is Texture)
                return "png";
            else if (obj is ComputeShader)
                return "compute";
            else if (obj is Shader)
                return "shader";
            else if (obj is Cubemap)
                return "cubemap";
            else if (obj is Flare)
                return "flare";
            else if (obj is ShaderVariantCollection)
                return "shadervariants";
            else if (obj is LightmapParameters)
                return "giparams";
            else if (obj is GUISkin)
                return "guiskin";
            else if (obj is PhysicMaterial)
                return "physicMaterial";
            else if (obj is UnityEngine.Audio.AudioMixer)
                return "mixer";
            else if (obj is TextAsset)
                return "txt";
            else if (obj is GameObject)
                return "prefab";
            else if (obj is ScriptableObject)
                return "asset";
            return "";
        }
    }
}

builder-main avatar Jun 12 '24 19:06 builder-main