Beispiel #1
0
        static bool IsTargetOrNested(SearchArg target, Object suspect)
        {
            if (!suspect)
            {
                return(false);
            }

            if (target.Target.GetInstanceID() == suspect.GetInstanceID() || target.Main.GetInstanceID() == (suspect).GetInstanceID())
            {
                return(true);
            }

            if (target.SubAssets.TryGet(out var subassets))
            {
                foreach (var asset in subassets)
                {
                    if (asset.GetInstanceID() == (suspect).GetInstanceID())
                    {
                        return(true);
                    }
                }
            }

            return(false);
        }
Beispiel #2
0
 public static void Upd(SearchArg arg)
 {
     if (arg.Target is DefaultAsset folder)
     {
         var path  = AssetDatabase.GetAssetPath(folder);
         var store = Globals <BacklinkStore> .Value;
         arg.UnusedAssetsFiltered = store.UnusedFiles.Where(p => p.Key.StartsWith(path)).Select(p => p.Key).ToList();
         arg.UnusedScenesFiltered = store.UnusedScenes.Where(p => p.Key.StartsWith(path)).Select(p => p.Key).ToList();
     }
 }
Beispiel #3
0
        // todo provide explicit scene arg
        public static void InScene(SearchArg arg, Scene currentScene)
        {
            var rootGameObjects = currentScene.GetRootGameObjects();

            foreach (var suspect in Traverse(rootGameObjects))
            {
                if (!SearchInChildProperties(arg, suspect, scene: true, out var entity))
                {
                    continue;
                }
                var s = entity.Set <InSceneResult>();
                s.ScenePath = arg.Scene.path;
            }

            IEnumerable <Object> Traverse(GameObject[] roots)
            {
                foreach (var rootGo in roots)
                {
                    foreach (var comp in GoAndChildComps(rootGo))
                    {
                        yield return(comp);
                    }
                }
            }

            IEnumerable <Object> GoAndChildComps(GameObject rr)
            {
                using (ComponentListPool.GetScoped(out var pooled)) {
                    rr.GetComponents(pooled);
                    foreach (var component in pooled)
                    {
                        if (component is Transform)
                        {
                            continue;
                        }
                        yield return(component);
                    }
                }

                var transform  = rr.transform;
                var childCount = transform.childCount;

                for (int i = 0; i < childCount; i++)
                {
                    var g = transform.GetChild(i).gameObject;
                    foreach (var res in GoAndChildComps(g))
                    {
                        yield return(res);
                    }
                }
            }
        }
Beispiel #4
0
        static void InScene(SearchArg arg, Scene currentScene)
        {
            var allObjects = currentScene
                             .GetRootGameObjects()
                             .SelectMany(g => g.GetComponentsInChildren <Component>(true)
                                         .Where(c => c && !(c is Transform))
                                         .Union(Enumerable.Repeat(g as Object, 1))
                                         )
                             .ToArray();

            var total = allObjects.Length;

            for (var i = 0; i < total; i++)
            {
                var suspect = allObjects[i];

                if (SearchInChildProperties(arg, suspect, true, out var entity))
                {
                    var s = entity.Set <InSceneResult>();
                    s.ScenePath = arg.Scene.path;
                }
            }
        }
Beispiel #5
0
        public static void Init(SearchArg arg, Object target, Scene scene = default)
        {
            Asr.IsNotNull(target, "Asset you're trying to search is corrupted");

            arg.Target = target;

            arg.FilePath = AssetDatabase.GetAssetPath(arg.Target);
            if (!scene.IsValid())
            {
                Upd(arg);

                arg.Main = AssetDatabase.LoadMainAssetAtPath(arg.FilePath);
                if (AssetDatabase.IsSubAsset(arg.Target))
                {
                }
                else
                {
                    switch (target)
                    {
                    case SceneAsset _:
                        // todo support cross-scene references?
                        // nested = all assets
                        break;

                    default:
                        // AssetDatabase.IsMainAssetAtPathLoaded()
                        var subAssets = AssetDatabase.LoadAllAssetsAtPath(arg.FilePath).Where(Predicate).ToArray();
                        arg.SubAssets = subAssets.Length == 0 ? default(Option <Object[]>) : subAssets;

                        bool Predicate(Object s)
                        {
                            if (!s)
                            {
                                return(false);
                            }
                            return(s.GetInstanceID() != arg.Target.GetInstanceID());
                        }

                        break;
                    }
                }
            }
            else
            {
                switch (arg.Target)
                {
                case GameObject gg:
                    arg.Main      = gg;
                    arg.Scene     = scene;
                    arg.SubAssets = gg.GetComponents <Component>().OfType <Object>().ToArray();
                    break;

                case Component component: {
                    // treat like subAsset
                    arg.Main  = component.gameObject;
                    arg.Scene = scene;
                    break;
                }

                default:
                    // project asset such as Material
                    arg.Main  = arg.Target;
                    arg.Scene = scene;
                    break;
                }
            }
        }
Beispiel #6
0
        public static void FilesThatReference(SearchArg arg)
        {
            var store = Globals <BacklinkStore> .Value;
            var path1 = AssetDatabase.GetAssetPath(arg.Target);

            if (!store.Backward.TryGetValue(path1, out var dep))
            {
                return;
            }

            foreach (var path in dep.Lookup)
            {
                var mainAsset = AssetDatabase.GetMainAssetTypeAtPath(path);
                if (mainAsset.IsAssignableFromInverse(typeof(SceneAsset)))
                {
                    continue;
                }

                var any = false;
                if (mainAsset.IsAssignableFromInverse(typeof(GameObject)))
                {
                }
                else
                {
                    var allAssetsAtPath = AssetDatabase.LoadAllAssetsAtPath(path);
                    foreach (var suspect in allAssetsAtPath)
                    {
                        if (suspect is DefaultAsset || suspect is Transform || !suspect)
                        {
                            continue;
                        }

                        if (!SearchInChildProperties(arg, suspect, false, out var entity))
                        {
                            continue;
                        }

                        entity.Set <FileResultTag>();
                        any = true;
                    }
                }

                if (any)
                {
                    continue;
                }

                // failed to find any property - just show main asset
                var e   = World.NewEntity();
                var gui = e.Set <SearchResultGui>();
                gui.Properties = new List <SearchResultGui.PropertyData>();
                var main = AssetDatabase.LoadMainAssetAtPath(path);
                gui.Label = new GUIContent()
                {
                    image = AssetPreview.GetMiniThumbnail(main),
                    text  = path.Replace(AssetsRootPath, string.Empty)
                };
                var res = e.Set <Result>();
                res.MainFile = main;
                e.Set <FileResultTag>();
            }
        }
Beispiel #7
0
        static bool SearchInChildProperties(SearchArg arg, Object suspect, bool scene, out EcsEntity entity)
        {
            if (IsTargetOrNested(arg, suspect))
            {
                entity = default;
                return(false);
            }

            if (!suspect)
            {
                entity = default;
                return(false);
            }

            var so = new SerializedObject(suspect);

            _tmp.Clear();
            var queue        = _tmp;
            var propIterator = so.GetIterator();

            var prefabInstance = false;

            if (scene && !string.IsNullOrEmpty(arg.FilePath) && PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(suspect) == arg.FilePath)
            {
                prefabInstance = true;
                while (propIterator.NextVisible(true))
                {
                    if (propIterator.propertyType != SerializedPropertyType.ObjectReference)
                    {
                        continue;
                    }
                    if (!IsTargetOrNested(arg, propIterator.objectReferenceValue))
                    {
                        continue;
                    }

                    queue.Enqueue(propIterator.Copy());
                }
            }
            else
            {
                while (propIterator.Next(true))
                {
                    if (propIterator.propertyType != SerializedPropertyType.ObjectReference)
                    {
                        continue;
                    }
                    if (!IsTargetOrNested(arg, propIterator.objectReferenceValue))
                    {
                        continue;
                    }

                    queue.Enqueue(propIterator.Copy());
                }
            }

            if (queue.Count == 0 && !prefabInstance)
            {
                entity = default;
                return(false);
            }

            entity = World.NewEntityWith(out Result data);

            var gui = entity.Set <SearchResultGui>();

            gui.Properties       = new List <SearchResultGui.PropertyData>();
            gui.SerializedObject = so;
            gui.Label            = new GUIContent();

            // init header
            Texture2D miniTypeThumbnail = null;

            if (scene)
            {
                switch (suspect)
                {
                case Component component:
                    data.RootGo       = component.gameObject;
                    gui.TransformPath = AnimationUtility.CalculateTransformPath(component.transform, null);
                    gui.Label.image   = AssetPreview.GetMiniThumbnail(data.RootGo);
                    gui.Label.text    = gui.TransformPath;
                    break;

                case GameObject go:
                    data.RootGo       = go;
                    gui.Label.image   = AssetPreview.GetMiniThumbnail(data.RootGo);
                    gui.TransformPath = AnimationUtility.CalculateTransformPath(go.transform, null);
                    gui.Label.text    = gui.TransformPath;
                    break;

                default:
                    throw new NotImplementedException();
                }

                miniTypeThumbnail = data.RootGo.GetInstanceID() == suspect.GetInstanceID()
                                        ? null
                                        : AssetPreview.GetMiniThumbnail(suspect);
            }
            else
            {
                data.File     = suspect;
                data.FilePath = AssetDatabase.GetAssetPath(data.File);
                data.MainFile = AssetDatabase.LoadMainAssetAtPath(data.FilePath);

                // todo
                var prefabInstanceStatus = PrefabUtility.GetPrefabInstanceStatus(data.MainFile);
                switch (prefabInstanceStatus)
                {
                case PrefabInstanceStatus.Connected:
                case PrefabInstanceStatus.Disconnected:
                    switch (data.File)
                    {
                    case Component comp:
                        // transformPath = $"{AnimationUtility.CalculateTransformPath(comp.transform, null)}/".Replace("/", "/\n");
                        gui.TransformPath =
                            $"{AnimationUtility.CalculateTransformPath(comp.transform, null)}";
                        break;

                    case GameObject go:
                        // transformPath = $"{AnimationUtility.CalculateTransformPath(go.transform, null)}/".Replace("/", "/\n");
                        gui.TransformPath =
                            $"{AnimationUtility.CalculateTransformPath(go.transform, null)}";
                        break;

                    default:
                        // Assert.Fail("Not a component"); //todo
                        break;
                    }

                    break;

                case PrefabInstanceStatus.NotAPrefab:
                case PrefabInstanceStatus.MissingAsset:
                    if (!AssetDatabase.IsMainAsset(data.File))
                    {
                        // {row.Main.name}
                        gui.TransformPath = $"/{data.File.name}";
                    }

                    break;
                }

                gui.Label.text  = data.FilePath.Replace(AssetsRootPath, string.Empty);
                gui.Label.image = AssetDatabase.GetCachedIcon(data.FilePath);
            }

            gui.Label.tooltip = gui.TransformPath;


            // init properties (footer)
            while (queue.Count > 0)
            {
                var prop         = queue.Dequeue();
                var targetObject = prop.serializedObject.targetObject;
                var item         = new SearchResultGui.PropertyData {
                    Property = prop,
                    Content  = new GUIContent()
                };
                item.Content.image   = miniTypeThumbnail;
                item.Content.text    = Nicify(prop, targetObject, gui.TransformPath);
                item.Content.tooltip = gui.TransformPath;
                var typeName = targetObject.GetType().Name;
                if (StringComparer.Ordinal.Equals(typeName, targetObject.name))
                {
                    item.Content.tooltip = $"{gui.TransformPath}.{prop.propertyPath}";
                }
                else
                {
                    item.Content.tooltip = $"{gui.TransformPath}({typeName}).{prop.propertyPath}";
                }
                gui.Properties.Add(item: item);
            }

            return(true);
        }
        static bool AskDeleteUnusedScenes(SearchArg arg, List <string> unusedScenes, WindowData windowData)
        {
            if (arg == null || unusedScenes == null)
            {
                return(false);
            }
            var hasUnusedScenes = unusedScenes.Count > 0;
            var previous        = GUI.enabled;

            GUI.enabled = hasUnusedScenes;

            var guiContentRemoveScenes = windowData.Style.RemoveScene;

            if (GUILayout.Button(guiContentRemoveScenes,
                                 windowData.Style.RemoveUnusedBtn))
            {
                var choice = EditorUtility.DisplayDialog(
                    title: "Asset Cleaner",
                    message:
                    $"Do you really want to remove {unusedScenes.Count} scene(s)?",
                    ok: "Remove",
                    cancel: "Cancel");
                if (choice)
                {
                    EditorApplication.ExecuteMenuItem("File/Save Project");

                    var i     = 0f;
                    var total = (float)unusedScenes.Count;

                    foreach (var scene in unusedScenes)
                    {
                        var path = Application.dataPath.Replace("Assets", scene);
                        DeleteWithMeta(path);

                        if (total >= _progressBarShowFromLevel)
                        {
                            var percent = i * 100 / total;
                            if (Math.Abs(percent % 5f) < 0.01f)
                            {
                                if (EditorUtility.DisplayCancelableProgressBar(
                                        "Please wait...",
                                        "Deleting scenes...", percent))
                                {
                                    throw new Exception("Deleting aborted");
                                }
                            }

                            i++;
                        }
                    }

                    if (total >= _progressBarShowFromLevel)
                    {
                        EditorUtility.ClearProgressBar();
                    }

                    AssetDatabase.Refresh();
                    SearchUtils.Upd(arg);
                }

                GUI.enabled = previous;

                return(true);
            }

            GUI.enabled = previous;

            return(false);
        }
        BacklinkStore.UnusedQty ShowObjectName(BacklinkStore store, WindowData windowData, TargetTypeEnum targetTypeEnum, SearchArg arg, string[] selectedGuids)
        {
            float TextWidth()
            {
                _buf2.text = _contentBuf.text;
                return(20f + GUI.skin.button.CalcSize(_buf2).x);
            }

            if (arg == null || arg.Main == null)
            {
                return(new BacklinkStore.UnusedQty());
            }

            bool isMultiSelect = selectedGuids != null && selectedGuids.Length > 1;

            if (_contentBuf == null)
            {
                _contentBuf = new GUIContent {
                    tooltip = $"Click to ping"
                };
            }

            _contentBuf.image = isMultiSelect
                ? windowData.Style.MultiSelect.image
                : AssetPreview.GetMiniThumbnail(arg.Target);
            _contentBuf.text    = string.Empty;
            _contentBuf.tooltip = string.Empty;

            if (!isMultiSelect)
            {
                switch (targetTypeEnum)
                {
                case TargetTypeEnum.Directory:
                    if (!SearchArgMain.IsEmpty())
                    {
                        _contentBuf.text = $"{arg.Main.name} (Folder)";
                        if (GUILayout.Button(_contentBuf, windowData.Style.CurrentBtn,
                                             GUILayout.MinWidth(TextWidth())))
                        {
                            EditorGUIUtility.PingObject(arg.Main);
                        }

                        if (AskDeleteUnusedFiles(arg, arg.UnusedAssetsFiltered, windowData))
                        {
                            return(new BacklinkStore.UnusedQty());
                        }

                        if (AskDeleteUnusedScenes(arg, arg.UnusedScenesFiltered, windowData))
                        {
                            return(new BacklinkStore.UnusedQty());
                        }
                    }

                    break;

                case TargetTypeEnum.File:
                    _contentBuf.text = $"{arg.Main.name} (File Asset)";
                    if (GUILayout.Button(_contentBuf, windowData.Style.CurrentBtn,
                                         GUILayout.MinWidth(TextWidth())))
                    {
                        EditorGUIUtility.PingObject(arg.Main);
                    }

                    bool hasUnusedFile = SearchUtils.IsUnused(arg.FilePath);
                    var  previous      = GUI.enabled;
                    GUI.enabled = hasUnusedFile;

                    if (GUILayout.Button(windowData.Style.RemoveFile,
                                         windowData.Style.RemoveUnusedBtn))
                    {
                        var choice = EditorUtility.DisplayDialog(
                            title: "Asset Cleaner",
                            message:
                            $"Do you really want to remove file: \"{arg.Main.name}\"?",
                            ok: "Remove",
                            cancel: "Cancel");
                        if (choice)
                        {
                            EditorApplication.ExecuteMenuItem("File/Save Project");
                            DeleteWithMeta(arg.FilePath);
                            AssetDatabase.Refresh();
                            SearchUtils.Upd(arg);
                        }
                    }

                    GUI.enabled = previous;

                    break;

                case TargetTypeEnum.Scene:
                    _contentBuf.text = $"{arg.Main.name} (Scene)";
                    if (GUILayout.Button(_contentBuf, windowData.Style.CurrentBtn,
                                         GUILayout.MinWidth(TextWidth())))
                    {
                        EditorGUIUtility.PingObject(arg.Main);
                    }

                    bool hasUnusedScene = SearchUtils.IsUnused(arg.FilePath);
                    previous    = GUI.enabled;
                    GUI.enabled = hasUnusedScene;

                    if (GUILayout.Button(windowData.Style.RemoveScene,
                                         windowData.Style.RemoveUnusedBtn))
                    {
                        var choice = EditorUtility.DisplayDialog(
                            title: "Asset Cleaner",
                            message:
                            $"Do you really want to remove scene: {arg.Main.name}?",
                            ok: "Remove",
                            cancel: "Cancel");
                        if (choice)
                        {
                            EditorApplication.ExecuteMenuItem("File/Save Project");
                            DeleteWithMeta(arg.FilePath);
                            AssetDatabase.Refresh();
                            SearchUtils.Upd(arg);
                        }
                    }

                    GUI.enabled = previous;
                    break;

                case TargetTypeEnum.ObjectInScene:
                    _contentBuf.text = $"{arg.Main.name} (Object in Scene)";

                    if (GUILayout.Button(_contentBuf, windowData.Style.CurrentBtn,
                                         GUILayout.MinWidth(TextWidth())))
                    {
                        EditorGUIUtility.PingObject(arg.Main);
                    }

                    break;

                case TargetTypeEnum.ObjectInStage:
                    _contentBuf.image = AssetPreview.GetMiniThumbnail(arg.Target);
                    _contentBuf.text  = $"{arg.Main.name} (Object in Staging)";

                    if (GUILayout.Button(_contentBuf,
                                         windowData.Style.RemoveUnusedBtn))
                    {
                        EditorGUIUtility.PingObject(arg.Main);
                    }

                    break;

                default:
                    if (GUILayout.Button($"{arg.Main.name} (Unknown Object Type)",
                                         windowData.Style.RemoveUnusedBtn))
                    {
                        EditorGUIUtility.PingObject(arg.Main);
                    }

                    break;
                }
            }
            else
            {
                var unusedAssets = new List <string>();
                var unusedScenes = new List <string>();

                foreach (var guid in selectedGuids)
                {
                    var path = AssetDatabase.GUIDToAssetPath(guid);

                    if (store.UnusedFiles.TryGetValue(path, out _))
                    {
                        unusedAssets.Add(path);
                    }

                    else if (store.UnusedScenes.TryGetValue(path, out _))
                    {
                        unusedScenes.Add(path);
                    }

                    if (store.FoldersWithQty.TryGetValue(path, out _))
                    {
                        SearchArg searchArg = new SearchArg()
                        {
                            FilePath = path,
                            Target   = AssetDatabase.LoadAssetAtPath <DefaultAsset>(path),
                            Main     = AssetDatabase.LoadAssetAtPath <DefaultAsset>(path)
                        };
                        SearchUtils.Upd(searchArg);

                        foreach (var unusedAssetPath in searchArg.UnusedAssetsFiltered)
                        {
                            if (store.UnusedFiles.TryGetValue(unusedAssetPath, out _))
                            {
                                unusedAssets.Add(unusedAssetPath);
                            }
                        }

                        foreach (var unusedScenePath in searchArg.UnusedScenesFiltered)
                        {
                            if (store.UnusedScenes.TryGetValue(unusedScenePath, out _))
                            {
                                unusedScenes.Add(unusedScenePath);
                            }
                        }
                    }
                }

                unusedAssets = unusedAssets.Distinct().ToList();
                unusedScenes = unusedScenes.Distinct().ToList();

                var assetSize = unusedAssets.Sum(CommonUtils.Size);
                var sceneSize = unusedScenes.Sum(CommonUtils.Size);

                _contentBuf.text =
                    $"Assets: {unusedAssets.Count} ({CommonUtils.BytesToString(assetSize)}), Scenes: {unusedScenes.Count} ({CommonUtils.BytesToString(sceneSize)})";
                ;

                GUILayout.Button(_contentBuf, windowData.Style.CurrentBtn, GUILayout.MinWidth(TextWidth()));

                if (AskDeleteUnusedFiles(arg, unusedAssets, windowData))
                {
                    return(new BacklinkStore.UnusedQty());
                }

                if (AskDeleteUnusedScenes(arg, unusedScenes, windowData))
                {
                    return(new BacklinkStore.UnusedQty());
                }

                return(new BacklinkStore.UnusedQty(unusedAssets.Count, unusedScenes.Count, assetSize + sceneSize));
            }

            return(new BacklinkStore.UnusedQty());
        }
        static bool TryGetHelpInfo(SearchArg arg, out string msg, out MessageType msgType)
        {
            msgType = MessageType.Info;
            if (arg == null)
            {
                msg = default;
                return(false);
            }

            var path = arg.FilePath;

            if (string.IsNullOrEmpty(path))
            {
                msg = default;
                return(false);
            }

            if (SearchUtils.IgnoredPaths(path, out var subPath))
            {
                msg = $"Paths containing '{subPath}' are ignored. You could add or remove it in Settings tab";
                return(true);
            }

            if (SearchUtils.IgnoredNonAssets(path) && !path.Eq("Assets"))
            {
                msg = $"Asset is outside of Assets folder";
                return(true);
            }

            if (IgnoreTypes.Check(path, out var type))
            {
                if (AssetDatabase.IsValidFolder(path))
                {
                    var scenes = arg.UnusedScenesFiltered?.Count;
                    var files  = arg.UnusedAssetsFiltered?.Count;
                    if (scenes == 0 && files == 0)
                    {
                        msg = default;
                        return(false);
                    }

                    var b = new StringBuilder();
                    b.Append("This directory contains: ");
                    var any = false;

                    if (files > 0)
                    {
                        any = true;

                        b.Append($"unused files ({files})");
                    }

                    if (scenes > 0)
                    {
                        if (any)
                        {
                            b.Append(", ");
                        }
                        b.Append($"unused scenes ({scenes})");
                    }

                    b.Append(
                        ".\nYou could delete them pressing corresponding button to the right.\nIf you don't want it to be inspected, please add it to Ignore list in the Settings tab");
                    msg = b.ToString();
                    return(true);
                }

                msg = $"Assets of type '{type.Name}' are ignored. Please contact support if you need to change it";
                return(true);
            }

            // if (Filters.ScenePaths.GetEntitiesCount() == 0 && Filters.FileResultRows.GetEntitiesCount() == 0) {
            if (SearchUtils.IsUnused(path))
            {
                type = AssetDatabase.GetMainAssetTypeAtPath(path);
                var name = type.IsAssignableFromInverse(typeof(SceneAsset)) ? "scene" : "file";
                msg =
                    $"This {name} has no explicit serialized usages and potentially could be removed. If you don't want it to be inspected, please add the containing folder to ignore list";
                return(true);
            }

            msgType = default;
            msg     = default;
            return(false);
        }
        void ShowTabMain(Config conf, WindowData windowData)
        {
            var store = Globals <BacklinkStore> .Value;

            EditorGUIUtility.labelWidth = windowData.Window.position.width * .7f;

            int Hash() => DirtyUtils.HashCode(conf.Locked);

            var active = SearchArgMain.Get1[0];

            if (conf.Locked && (windowData.FindFrom == FindModeEnum.File &&
                                (active == null || active.Main == null || !AssetDatabase.Contains(active.Main))))
            {
                conf.Locked = false;
                AufCtx.World.NewEntityWith(out RequestRepaintEvt _);
            }

            var style = windowData.Style;
            var hash  = Hash();

            if (hash != Hash())
            {
                PersistenceUtils.Save(in conf);
                AufCtx.World.NewEntityWith(out RequestRepaintEvt _);
            }

            // if (Globals<WindowData>.Get() == null) return;
            EditorGUILayout.Space();

            SearchArg arg = default;

            foreach (var i in SearchArgMain)
            {
                arg = SearchArgMain.Get1[i];
                if (arg != null && arg.Main != null)
                {
                    break;
                }
            }

            if (arg == default)
            {
                return;
            }

            var targetTypeEnum = GetTargetType(windowData, arg?.Main);

            BacklinkStore.UnusedQty unusedQty = new BacklinkStore.UnusedQty(0, 0, 0);

            using (new EditorGUILayout.HorizontalScope()) {
                var enabledBuf    = GUI.enabled;
                var selectedGuids = Selection.assetGUIDs;

                var undoRedoState = Globals <UndoRedoState> .Value;

                GUI.enabled = selectedGuids != null && !conf.Locked && undoRedoState.UndoEnabled;
                if (GUILayout.Button(style.ArrowL, style.ArrowBtn))
                {
                    AufCtx.World.NewEntityWith(out UndoEvt _);
                }

                GUI.enabled = selectedGuids != null && !conf.Locked && undoRedoState.RedoEnabled;
                if (GUILayout.Button(style.ArrowR, style.ArrowBtn))
                {
                    AufCtx.World.NewEntityWith(out RedoEvt _);
                }

                GUI.enabled = enabledBuf;

                if (conf.Locked)
                {
                    if (GUILayout.Button(style.Lock, style.LockBtn))
                    {
                        AufCtx.World.NewEntityWith(out SelectionChanged selectionChanged);
                        conf.Locked = false;
                        if (Selection.activeObject != arg.Target)
                        {
                            selectionChanged.From   = FindModeEnum.Scene;
                            selectionChanged.Scene  = SceneManager.GetActiveScene();
                            selectionChanged.Target = Selection.activeObject;
                        }
                        else if (Selection.assetGUIDs is string[] guids)
                        {
                            // todo show info box multiple selection is unsupported
                            if (guids.Length > 0)
                            {
                                var path = AssetDatabase.GUIDToAssetPath(guids[0]);
                                selectionChanged.Target = AssetDatabase.LoadAssetAtPath <Object>(path);
                                switch (Selection.selectionChanged.Target)
                                {
                                case DefaultAsset _:
                                    selectionChanged.From = FindModeEnum.File;
                                    break;

                                case GameObject go when go.scene.isLoaded:
                                    selectionChanged.From  = FindModeEnum.Scene;
                                    selectionChanged.Scene = SceneManager.GetActiveScene();
                                    break;

                                default:
                                    selectionChanged.From = FindModeEnum.File;
                                    break;
                                }
                            }
                            else if (Selection.activeObject is GameObject go && go.scene.isLoaded)
                            {
                                selectionChanged.From   = FindModeEnum.Scene;
                                selectionChanged.Target = Selection.activeObject;
                                selectionChanged.Scene  = SceneManager.GetActiveScene();
                            }
                        }
                    }
                }
                else
                {
                    var enabled = GUI.enabled;
                    GUI.enabled = selectedGuids != null && selectedGuids.Length == 1;
                    if (GUILayout.Button(style.Unlock, style.UnlockBtn))
                    {
                        conf.Locked = true;
                    }

                    GUI.enabled = enabled;
                }

                unusedQty = ShowObjectName(store, windowData, targetTypeEnum, arg, selectedGuids);
            }

            bool isMultiSelect = Selection.assetGUIDs != null && Selection.assetGUIDs.Length > 1;

            if (conf.ShowInfoBox)
            {
                if (isMultiSelect && (unusedQty.UnusedFilesQty + unusedQty.UnusedScenesQty > 0))
                {
                    var msgUnusedFiles = (unusedQty.UnusedFilesQty > 0)
                        ? $"unused files ({unusedQty.UnusedFilesQty}),"
                        : "";
                    var msgUnusedScenes = (unusedQty.UnusedScenesQty > 0)
                        ? $"unused scenes ({unusedQty.UnusedScenesQty}),"
                        : "";
                    var msgMultiSelect = $"This multi-selection contains: " +
                                         msgUnusedFiles + msgUnusedScenes +
                                         $"\nYou could delete them pressing corresponding button to the right.";
                    EditorGUILayout.HelpBox(msgMultiSelect, MessageType.Info);
                }
                else if (TryGetHelpInfo(arg, out var msg, out var msgType))
                {
                    EditorGUILayout.HelpBox(msg, msgType);
                }
            }

            if (targetTypeEnum != TargetTypeEnum.Directory && !isMultiSelect)
            {
                var windowData2 = Globals <WindowData> .Value;

                EditorGUILayout.BeginVertical();
                {
                    windowData2.ScrollPos = EditorGUILayout.BeginScrollView(windowData2.ScrollPos);
                    {
                        RenderRows(windowData2); //TODO?
                        EditorGUILayout.Space();
                    }
                    EditorGUILayout.EndScrollView();
                }
                EditorGUILayout.EndVertical();
                EditorGUILayout.Space();
            }
        }