From b43c49bb32f01faccc5a8bf4ec371b848c9d01ce Mon Sep 17 00:00:00 2001 From: Bruno Mikoski Date: Tue, 18 Feb 2025 17:52:38 +0000 Subject: [PATCH 1/4] add some tryout to fix dupicating Collections --- .../CustomEditors/CollectionCustomEditor.cs | 59 ++--- .../CollectionAssetsModificationProcessor.cs | 5 +- .../Utils/SOCollectionsContextMenuItems.cs | 212 ++++++++++++++++++ .../SOCollectionsContextMenuItems.cs.meta | 3 + .../Core/ScriptableObjectCollection.cs | 58 +++-- Scripts/Runtime/Utils/SOCItemUtility.cs | 56 +++++ Scripts/Runtime/Utils/SOCItemUtility.cs.meta | 3 + 7 files changed, 336 insertions(+), 60 deletions(-) create mode 100644 Scripts/Editor/Utils/SOCollectionsContextMenuItems.cs create mode 100644 Scripts/Editor/Utils/SOCollectionsContextMenuItems.cs.meta create mode 100644 Scripts/Runtime/Utils/SOCItemUtility.cs create mode 100644 Scripts/Runtime/Utils/SOCItemUtility.cs.meta diff --git a/Scripts/Editor/CustomEditors/CollectionCustomEditor.cs b/Scripts/Editor/CustomEditors/CollectionCustomEditor.cs index 3225e4e..d0ecbaf 100644 --- a/Scripts/Editor/CustomEditors/CollectionCustomEditor.cs +++ b/Scripts/Editor/CustomEditors/CollectionCustomEditor.cs @@ -626,10 +626,10 @@ private bool IsAddressableAsset(ScriptableObject target) #endif } - private ScriptableObject AddNewItemOfType(Type targetType, bool autoFocusForRename = true) + private ScriptableObject AddNewItemOfType(Type targetType,string assetName = "", bool autoFocusForRename = true) { Undo.IncrementCurrentGroup(); - ScriptableObject newItem = collection.AddNew(targetType); + ScriptableObject newItem = collection.AddNew(targetType, assetName); Undo.RegisterCreatedObjectUndo(newItem, "Create New Item"); Undo.RecordObject(collection, "Add New Item"); Undo.SetCurrentGroupName($"Created new item {newItem.name}"); @@ -792,10 +792,12 @@ private void ShowOptionsForIndex(MouseUpEvent evt, int targetIndex) { DuplicateItem(item, false); } + ReloadFilteredItems(); } else { DuplicateItem(targetIndex); + ReloadFilteredItems(); } } ); @@ -825,6 +827,7 @@ private void ShowOptionsForIndex(MouseUpEvent evt, int targetIndex) { RemoveItemAtIndex(collection.IndexOf(item), result == 2); } + ReloadFilteredItems(); } else { @@ -837,6 +840,7 @@ private void ShowOptionsForIndex(MouseUpEvent evt, int targetIndex) } RemoveItemAtIndex(filteredItems.Count - 1, result == 2); + ReloadFilteredItems(); } } ); @@ -872,19 +876,20 @@ private void ShowOptionsForIndex(MouseUpEvent evt, int targetIndex) foreach (ScriptableObject item in moveItems) { - MoveItem(item, scriptableObjectCollection); + SOCItemUtility.MoveItem(item as ISOCItem, scriptableObjectCollection); } + ReloadFilteredItems(); } else { - if (!EditorUtility.DisplayDialog($"Move Item", + if (!EditorUtility.DisplayDialog("Move Item", $"Are you sure you want to move {filteredItems[^1].name}, from {AssetDatabase.GetAssetPath(collection)} to {AssetDatabase.GetAssetPath(scriptableObject)}", "Yes", "No")) { return; } - - MoveItem(filteredItems[targetIndex], scriptableObjectCollection); + SOCItemUtility.MoveItem(filteredItems[targetIndex], scriptableObjectCollection); + ReloadFilteredItems(); } } ); @@ -923,38 +928,6 @@ private void ShowOptionsForIndex(MouseUpEvent evt, int targetIndex) menu.ShowAsContext(); } - private void MoveItem(ScriptableObject item, ScriptableObjectCollection targetCollection) - { - Undo.RecordObject(collection, "Move Item"); - Undo.RecordObject(targetCollection, "Move Item"); - - collection.Remove(item); - targetCollection.Add(item); - - string itemPath = AssetDatabase.GetAssetPath(item); - string targetCollectionPath = AssetDatabase.GetAssetPath(targetCollection); - - if (!string.IsNullOrEmpty(itemPath) && !string.IsNullOrEmpty(targetCollectionPath)) - { - string directory = Path.GetDirectoryName(targetCollectionPath); - - string itemsFolderPath = Path.Combine(directory, "Items"); - bool hasItemsFolder = AssetDatabase.IsValidFolder(itemsFolderPath); - - string finalDirectory = hasItemsFolder ? itemsFolderPath : directory; - string fileName = Path.GetFileName(itemPath); - - string newPath = AssetDatabase.GenerateUniqueAssetPath(Path.Combine(finalDirectory, fileName)); - - AssetDatabase.MoveAsset(itemPath, newPath); - } - - AssetDatabase.SaveAssets(); - AssetDatabase.Refresh(); - - ReloadFilteredItems(); - } - private List GetPossibleAnotherCollections() { CollectionsRegistry.Instance.TryGetCollectionsOfItemType(collection.GetItemType(), out List collections); @@ -980,7 +953,14 @@ private void DuplicateItem(int index, bool showRenameAfter = true) private void DuplicateItem(ScriptableObject source, bool showRenameAfter) { CopyCollectionItemUtility.SetSource(source); - ScriptableObject newItem = AddNewItemOfType(source.GetType(), false); + + string path = AssetDatabase.GetAssetPath(source); + string directory = Path.GetDirectoryName(path); + string fileName = $"{source.name} (Clone)"; + + fileName = AssetDatabase.GenerateUniqueAssetPath(Path.Combine(directory, $"{fileName}.asset")); + + ScriptableObject newItem = AddNewItemOfType(source.GetType(), Path.GetFileNameWithoutExtension(fileName), false); CopyCollectionItemUtility.ApplySourceToTarget(newItem); if (showRenameAfter) @@ -990,7 +970,6 @@ private void DuplicateItem(ScriptableObject source, bool showRenameAfter) } else { - AssetDatabaseUtils.RenameAsset(newItem, $"{source.name} (Copy)"); AssetDatabase.SaveAssetIfDirty(newItem); ReloadFilteredItems(); } diff --git a/Scripts/Editor/Processors/CollectionAssetsModificationProcessor.cs b/Scripts/Editor/Processors/CollectionAssetsModificationProcessor.cs index 1ff9d5b..6fd6f1c 100644 --- a/Scripts/Editor/Processors/CollectionAssetsModificationProcessor.cs +++ b/Scripts/Editor/Processors/CollectionAssetsModificationProcessor.cs @@ -24,6 +24,9 @@ public static AssetDeleteResult OnWillDeleteAsset(string targetAssetPath, Remove if (socItem == null) return AssetDeleteResult.DidNotDelete; + if (socItem.Collection == null) + return AssetDeleteResult.DidNotDelete; + socItem.Collection.Remove(collectionItem); return AssetDeleteResult.DidNotDelete; } @@ -42,4 +45,4 @@ public static AssetDeleteResult OnWillDeleteAsset(string targetAssetPath, Remove } } -} +} \ No newline at end of file diff --git a/Scripts/Editor/Utils/SOCollectionsContextMenuItems.cs b/Scripts/Editor/Utils/SOCollectionsContextMenuItems.cs new file mode 100644 index 0000000..58ced08 --- /dev/null +++ b/Scripts/Editor/Utils/SOCollectionsContextMenuItems.cs @@ -0,0 +1,212 @@ +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace BrunoMikoski.ScriptableObjectCollections +{ + public static class SOCollectionsProjectContextMenus + { + // ================================ + // ISOCItem (ScriptableObjectCollectionItem) commands + // ================================ + + [MenuItem("Assets/ScriptableObjectCollections/ISOCItem/Move to Different Collection", true)] + private static bool ValidateMoveToDifferentCollection() + { + Object[] selectedObjects = Selection.objects; + if (selectedObjects == null || selectedObjects.Length == 0) + return false; + // Check that every selected object implements ISOCItem. + foreach (Object obj in selectedObjects) + { + ISOCItem socItem = obj as ISOCItem; + if (socItem == null) + return false; + } + return true; + } + + [MenuItem("Assets/ScriptableObjectCollections/ISOCItem/Move to Different Collection")] + private static void MoveToDifferentCollection() + { + Object[] selectedObjects = Selection.objects; + List items = new List(); + foreach (Object obj in selectedObjects) + { + if (obj is ISOCItem item) + items.Add(item); + } + if (items.Count == 0) + return; + + // Get available collections for the item type. + List possibleCollections = + CollectionsRegistry.Instance.GetCollectionsByItemType(items[0].GetType()); + if (possibleCollections == null || possibleCollections.Count == 0) + { + EditorUtility.DisplayDialog("Move to Different Collection", "No collections available.", "OK"); + return; + } + // Exclude the current collection of the first item. + ScriptableObjectCollection currentCollection = items[0].Collection; + List filteredCollections = new List(); + foreach (ScriptableObjectCollection collection in possibleCollections) + { + if (collection != currentCollection) + filteredCollections.Add(collection); + } + if (filteredCollections.Count == 0) + { + EditorUtility.DisplayDialog("Move to Different Collection", "No other collections available.", "OK"); + return; + } + // Present a GenericMenu so the user can choose a new collection. + GenericMenu menu = new GenericMenu(); + foreach (ScriptableObjectCollection collection in filteredCollections) + { + menu.AddItem(new GUIContent(collection.name), false, delegate + { + foreach (ISOCItem item in items) + { + SOCItemUtility.MoveItem(item, collection); + } + EditorUtility.SetDirty(collection); + }); + } + menu.ShowAsContext(); + } + + [MenuItem("Assets/ScriptableObjectCollections/ISOCItem/Select Collection", true)] + private static bool ValidateSelectCollection() + { + Object[] selectedObjects = Selection.objects; + if (selectedObjects == null || selectedObjects.Length != 1) + return false; + ISOCItem socItem = selectedObjects[0] as ISOCItem; + return socItem != null && socItem.Collection != null; + } + + [MenuItem("Assets/ScriptableObjectCollections/ISOCItem/Select Collection")] + private static void SelectCollection() + { + Object[] selectedObjects = Selection.objects; + if (selectedObjects == null || selectedObjects.Length != 1) + return; + ISOCItem socItem = selectedObjects[0] as ISOCItem; + if (socItem != null && socItem.Collection != null) + Selection.activeObject = socItem.Collection; + } + + [MenuItem("Assets/ScriptableObjectCollections/ISOCItem/Duplicate", true)] + private static bool ValidateDuplicateItem() + { + Object[] selectedObjects = Selection.objects; + if (selectedObjects == null || selectedObjects.Length == 0) + return false; + foreach (Object obj in selectedObjects) + { + if (!(obj is ScriptableObject)) + return false; + } + return true; + } + + [MenuItem("Assets/ScriptableObjectCollections/ISOCItem/Duplicate")] + private static void DuplicateItem() + { + Object[] selectedObjects = Selection.objects; + foreach (Object obj in selectedObjects) + { + string assetPath = AssetDatabase.GetAssetPath(obj); + if (string.IsNullOrEmpty(assetPath)) + continue; + string newPath = AssetDatabase.GenerateUniqueAssetPath(assetPath); + bool copySuccess = AssetDatabase.CopyAsset(assetPath, newPath); + if (copySuccess) + AssetDatabase.Refresh(); + } + } + + [MenuItem("Assets/ScriptableObjectCollections/ISOCItem/Delete", true)] + private static bool ValidateDeleteItem() + { + Object[] selectedObjects = Selection.objects; + if (selectedObjects == null || selectedObjects.Length == 0) + return false; + foreach (Object obj in selectedObjects) + { + if (!(obj is ScriptableObject)) + return false; + } + return true; + } + + [MenuItem("Assets/ScriptableObjectCollections/ISOCItem/Delete")] + private static void DeleteItem() + { + Object[] selectedObjects = Selection.objects; + if (EditorUtility.DisplayDialog("Delete Item", + "Are you sure you want to delete the selected item(s)?", "Yes", "No")) + { + List objectCollections = new List(); + foreach (Object obj in selectedObjects) + { + string assetPath = AssetDatabase.GetAssetPath(obj); + if(obj is ISOCItem socItem) + objectCollections.Add(socItem.Collection); + + if (!string.IsNullOrEmpty(assetPath)) + AssetDatabase.DeleteAsset(assetPath); + } + + foreach (ScriptableObjectCollection objectCollection in objectCollections) + { + objectCollection.RefreshCollection(); + } + + AssetDatabase.Refresh(); + } + } + + // ================================ + // ScriptableObjectCollection commands + // ================================ + + [MenuItem("Assets/ScriptableObjectCollections/ScriptableObjectCollection/Duplicate Collection", true)] + private static bool ValidateDuplicateCollection() + { + Object[] selectedObjects = Selection.objects; + if (selectedObjects == null || selectedObjects.Length != 1) + return false; + return selectedObjects[0] is ScriptableObjectCollection; + } + + [MenuItem("Assets/ScriptableObjectCollections/ScriptableObjectCollection/Duplicate Collection")] + private static void DuplicateCollection() + { + Object[] selectedObjects = Selection.objects; + if (selectedObjects == null || selectedObjects.Length != 1) + return; + ScriptableObjectCollection originalCollection = selectedObjects[0] as ScriptableObjectCollection; + if (originalCollection == null) + return; + string assetPath = AssetDatabase.GetAssetPath(originalCollection); + if (string.IsNullOrEmpty(assetPath)) + return; + string newPath = AssetDatabase.GenerateUniqueAssetPath(assetPath); + bool copySuccess = AssetDatabase.CopyAsset(assetPath, newPath); + if (copySuccess) + { + AssetDatabase.Refresh(); + CollectionsRegistry.Instance.ValidateCollections(); + EditorUtility.DisplayDialog("Duplicate Collection", + "Collection duplicated successfully at:\n" + newPath, "OK"); + } + else + { + EditorUtility.DisplayDialog("Duplicate Collection", + "Failed to duplicate collection.", "OK"); + } + } + } +} \ No newline at end of file diff --git a/Scripts/Editor/Utils/SOCollectionsContextMenuItems.cs.meta b/Scripts/Editor/Utils/SOCollectionsContextMenuItems.cs.meta new file mode 100644 index 0000000..03701b9 --- /dev/null +++ b/Scripts/Editor/Utils/SOCollectionsContextMenuItems.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6dd5680979ff4ebb958e1e776affe066 +timeCreated: 1739180519 \ No newline at end of file diff --git a/Scripts/Runtime/Core/ScriptableObjectCollection.cs b/Scripts/Runtime/Core/ScriptableObjectCollection.cs index 6e0efcc..daee54d 100644 --- a/Scripts/Runtime/Core/ScriptableObjectCollection.cs +++ b/Scripts/Runtime/Core/ScriptableObjectCollection.cs @@ -135,20 +135,10 @@ public ScriptableObject AddNew(Type itemType, string assetName = "") if (string.IsNullOrEmpty(itemName)) { - int count = Count; - while (true) - { - itemName = $"New{itemType.Name}{count}"; - string testPath = Path.Combine(parentFolderPath, itemName); - - if (!File.Exists(Path.GetFullPath($"{testPath}.asset"))) - break; - - count++; - } + itemName = $"{itemType.Name}"; } - newItem.name = itemName; + newItem.name = AssetDatabase.GenerateUniqueAssetPath(itemName); if(itemName.IsReservedKeyword()) Debug.LogError($"{itemName} is a reserved keyword name, will cause issues with code generation, please rename it"); @@ -321,8 +311,7 @@ public void Swap(int targetIndex, int newIndex) (items[targetIndex], items[newIndex]) = (items[newIndex], items[targetIndex]); ObjectUtility.SetDirty(this); } - - [ContextMenu("Refresh Collection")] + public void RefreshCollection() { #if UNITY_EDITOR @@ -334,15 +323,17 @@ public void RefreshCollection() string assetPath = AssetDatabase.GetAssetPath(this); if (string.IsNullOrEmpty(assetPath)) return; - + string folder = Path.GetDirectoryName(assetPath); string[] guids = AssetDatabase.FindAssets($"t:{collectionItemType.Name}", new []{folder}); + List itemsFromOtherCollections = new List(); + List itemsMissingCollection = new List(); for (int i = 0; i < guids.Length; i++) { ScriptableObject item = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guids[i])); - + if (item == null) continue; @@ -352,11 +343,18 @@ public void RefreshCollection() if (socItem.Collection != null) { if (socItem.Collection != this) + { + itemsFromOtherCollections.Add(socItem); continue; + } if (socItem.Collection.Contains(item)) continue; } + else + { + itemsMissingCollection.Add(socItem); + } if (Add(item)) changed = true; @@ -370,15 +368,37 @@ public void RefreshCollection() Debug.Log($"Removing item at index {i} as it is null"); changed = true; } - + ScriptableObject scriptableObject = items[i]; if (scriptableObject.GetType() == GetItemType() || scriptableObject.GetType().IsSubclassOf(GetItemType())) continue; - + RemoveAt(i); Debug.Log($"Removing item at index {i} {scriptableObject} since it is not of type {GetItemType()}"); } + if (itemsFromOtherCollections.Any()) + { + int result = EditorUtility.DisplayDialogComplex("Items from another collections", + $"The following items {string.Join(",", itemsFromOtherCollections.Select(o => o.name).ToArray())} belong to other collections, should I move to the appropriated folder?", + "Move to the assigned collection", $"Assign it to this collection ", "Do nothing"); + + if (result == 0) + { + foreach (ISOCItem itemsFromOtherCollection in itemsFromOtherCollections) + { + SOCItemUtility.MoveItem(itemsFromOtherCollection, itemsFromOtherCollection.Collection); + } + } + else if (result == 1) + { + foreach (ISOCItem itemsFromOtherCollection in itemsFromOtherCollections) + { + SOCItemUtility.MoveItem(itemsFromOtherCollection, this); + } + } + } + if (changed) ObjectUtility.SetDirty(this); #endif @@ -607,4 +627,4 @@ protected override void ClearCachedValues() cachedValues = null; } } -} +} \ No newline at end of file diff --git a/Scripts/Runtime/Utils/SOCItemUtility.cs b/Scripts/Runtime/Utils/SOCItemUtility.cs new file mode 100644 index 0000000..91bfbb0 --- /dev/null +++ b/Scripts/Runtime/Utils/SOCItemUtility.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; +#if UNITY_EDITOR +using UnityEditor; +#endif +using UnityEngine; + +namespace BrunoMikoski.ScriptableObjectCollections +{ + public static class SOCItemUtility + { + public static void MoveItem(ScriptableObject item, ScriptableObjectCollection targetCollection, + Action onCompleteCallback = null) + { + if (item is ISOCItem) + { + MoveItem(item, targetCollection, onCompleteCallback); + } + } + + public static void MoveItem(ISOCItem item, ScriptableObjectCollection targetCollection, Action onCompleteCallback = null) + { +#if UNITY_EDITOR + Undo.RecordObject(item.Collection, "Move Item"); + Undo.RecordObject(targetCollection, "Move Item"); + + item.Collection.Remove(item); + targetCollection.Add(item); + item.SetCollection(targetCollection); + + string itemPath = AssetDatabase.GetAssetPath(item as ScriptableObject); + string targetCollectionPath = AssetDatabase.GetAssetPath(targetCollection); + + if (!string.IsNullOrEmpty(itemPath) && !string.IsNullOrEmpty(targetCollectionPath)) + { + string directory = Path.GetDirectoryName(targetCollectionPath); + + string itemsFolderPath = Path.Combine(directory, "Items"); + bool hasItemsFolder = AssetDatabase.IsValidFolder(itemsFolderPath); + + string finalDirectory = hasItemsFolder ? itemsFolderPath : directory; + string fileName = Path.GetFileName(itemPath); + + string newPath = AssetDatabase.GenerateUniqueAssetPath(Path.Combine(finalDirectory, fileName)); + + AssetDatabase.MoveAsset(itemPath, newPath); + } + + AssetDatabase.SaveAssets(); + AssetDatabase.Refresh(); + + onCompleteCallback?.Invoke(); +#endif + } + } +} \ No newline at end of file diff --git a/Scripts/Runtime/Utils/SOCItemUtility.cs.meta b/Scripts/Runtime/Utils/SOCItemUtility.cs.meta new file mode 100644 index 0000000..569f104 --- /dev/null +++ b/Scripts/Runtime/Utils/SOCItemUtility.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f5f5245140e749748251516813de5ebe +timeCreated: 1739121561 \ No newline at end of file From af43b246f5aa29e02bdaf3888e2d5d9f1e838290 Mon Sep 17 00:00:00 2001 From: Bruno Mikoski Date: Tue, 18 Feb 2025 19:23:20 +0000 Subject: [PATCH 2/4] fix: some other stuffs --- Scripts/Runtime/Core/ISOCItem.cs | 3 ++- Scripts/Runtime/Core/ScriptableObjectCollection.cs | 7 ++++++- Scripts/Runtime/Core/ScriptableObjectCollectionItem.cs | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Scripts/Runtime/Core/ISOCItem.cs b/Scripts/Runtime/Core/ISOCItem.cs index a499c66..1a4c50b 100644 --- a/Scripts/Runtime/Core/ISOCItem.cs +++ b/Scripts/Runtime/Core/ISOCItem.cs @@ -9,10 +9,11 @@ public interface ISOCItem string name { get; set; } void SetCollection(ScriptableObjectCollection collection); void GenerateNewGUID(); + void ClearCollection(); } public interface ISOCColorizedItem { Color LabelColor { get;} } -} +} \ No newline at end of file diff --git a/Scripts/Runtime/Core/ScriptableObjectCollection.cs b/Scripts/Runtime/Core/ScriptableObjectCollection.cs index daee54d..3191518 100644 --- a/Scripts/Runtime/Core/ScriptableObjectCollection.cs +++ b/Scripts/Runtime/Core/ScriptableObjectCollection.cs @@ -272,6 +272,9 @@ public void Insert(int index, object value) public bool Remove(ScriptableObject item) { bool result = items.Remove(item); + if (item is ISOCItem socItem) + socItem.ClearCollection(); + ObjectUtility.SetDirty(this); return result; } @@ -360,13 +363,15 @@ public void RefreshCollection() changed = true; } - for (int i = items.Count - 1; i >= 0; i--) + int itemsCount = items.Count; + for (int i = itemsCount - 1; i >= 0; i--) { if (items[i] == null) { RemoveAt(i); Debug.Log($"Removing item at index {i} as it is null"); changed = true; + continue; } ScriptableObject scriptableObject = items[i]; diff --git a/Scripts/Runtime/Core/ScriptableObjectCollectionItem.cs b/Scripts/Runtime/Core/ScriptableObjectCollectionItem.cs index ed75e14..4919883 100644 --- a/Scripts/Runtime/Core/ScriptableObjectCollectionItem.cs +++ b/Scripts/Runtime/Core/ScriptableObjectCollectionItem.cs @@ -79,6 +79,7 @@ public void SetCollection(ScriptableObjectCollection collection) public void ClearCollection() { cachedCollection = null; + hasCachedCollection = false; collectionGUID = default; ObjectUtility.SetDirty(this); } From 27ad1af1eda66a4f3d004535e75dff20c4f3fc30 Mon Sep 17 00:00:00 2001 From: Bruno Mikoski Date: Wed, 19 Feb 2025 09:17:51 +0000 Subject: [PATCH 3/4] add: more stuffs --- .../CustomEditors/CollectionCustomEditor.cs | 7 +- .../CustomEditors/MoveToCollectionWindow.cs | 45 +++++ .../MoveToCollectionWindow.cs.meta | 3 + .../CollectionsAssetsPostProcessor.cs | 44 +++-- .../CollectionReferenceLongGuidDrawer.cs | 29 ++++ .../CollectionReferenceLongGuidDrawer.cs.meta | 3 + .../Utils/SOCollectionsContextMenuItems.cs | 160 +++--------------- .../CollectionReferenceLongGuidAttribute.cs | 11 ++ ...llectionReferenceLongGuidAttribute.cs.meta | 3 + .../SOCItemEditorOptionsAttribute.cs | 2 +- Scripts/Runtime/Core/CollectionsRegistry.cs | 30 ++++ .../Core/ScriptableObjectCollection.cs | 39 ++++- .../Core/ScriptableObjectCollectionItem.cs | 2 +- 13 files changed, 208 insertions(+), 170 deletions(-) create mode 100644 Scripts/Editor/CustomEditors/MoveToCollectionWindow.cs create mode 100644 Scripts/Editor/CustomEditors/MoveToCollectionWindow.cs.meta create mode 100644 Scripts/Editor/PropertyDrawers/CollectionReferenceLongGuidDrawer.cs create mode 100644 Scripts/Editor/PropertyDrawers/CollectionReferenceLongGuidDrawer.cs.meta create mode 100644 Scripts/Runtime/Attributes/CollectionReferenceLongGuidAttribute.cs create mode 100644 Scripts/Runtime/Attributes/CollectionReferenceLongGuidAttribute.cs.meta diff --git a/Scripts/Editor/CustomEditors/CollectionCustomEditor.cs b/Scripts/Editor/CustomEditors/CollectionCustomEditor.cs index d0ecbaf..5a3d46c 100644 --- a/Scripts/Editor/CustomEditors/CollectionCustomEditor.cs +++ b/Scripts/Editor/CustomEditors/CollectionCustomEditor.cs @@ -19,7 +19,6 @@ namespace BrunoMikoski.ScriptableObjectCollections { - [CustomEditor(typeof(ScriptableObjectCollection), true)] public class CollectionCustomEditor : Editor { @@ -394,6 +393,12 @@ protected virtual void OnEnable() { collection = (ScriptableObjectCollection)target; + if (!CollectionsRegistry.Instance.HasUniqueGUID(collection)) + { + collection.GenerateNewGUID(); + collection.Clear(); + } + if (!CollectionsRegistry.Instance.IsKnowCollection(collection)) CollectionsRegistry.Instance.ReloadCollections(); diff --git a/Scripts/Editor/CustomEditors/MoveToCollectionWindow.cs b/Scripts/Editor/CustomEditors/MoveToCollectionWindow.cs new file mode 100644 index 0000000..93d294a --- /dev/null +++ b/Scripts/Editor/CustomEditors/MoveToCollectionWindow.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace BrunoMikoski.ScriptableObjectCollections +{ + public class MoveToCollectionWindow : EditorWindow + { + private List itemsToMove; + private List availableCollections; + + public static void ShowWindow(List items, List collections) + { + MoveToCollectionWindow window = GetWindow("Move to Collection"); + window.itemsToMove = items; + window.availableCollections = collections; + window.ShowPopup(); + } + + private void OnGUI() + { + EditorGUILayout.LabelField("Select a Collection to Move Items", EditorStyles.boldLabel); + + if (availableCollections == null || availableCollections.Count == 0) + { + EditorGUILayout.LabelField("No available collections."); + return; + } + + foreach (ScriptableObjectCollection collection in availableCollections) + { + if (GUILayout.Button(collection.name)) + { + foreach (ISOCItem item in itemsToMove) + { + SOCItemUtility.MoveItem(item, collection); + EditorUtility.SetDirty(collection); + } + + Close(); + } + } + } + } +} \ No newline at end of file diff --git a/Scripts/Editor/CustomEditors/MoveToCollectionWindow.cs.meta b/Scripts/Editor/CustomEditors/MoveToCollectionWindow.cs.meta new file mode 100644 index 0000000..1c64435 --- /dev/null +++ b/Scripts/Editor/CustomEditors/MoveToCollectionWindow.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 517dfb63fdfc446eb055a5e2859396b1 +timeCreated: 1739956461 \ No newline at end of file diff --git a/Scripts/Editor/Processors/CollectionsAssetsPostProcessor.cs b/Scripts/Editor/Processors/CollectionsAssetsPostProcessor.cs index 78c9f2e..6d61465 100644 --- a/Scripts/Editor/Processors/CollectionsAssetsPostProcessor.cs +++ b/Scripts/Editor/Processors/CollectionsAssetsPostProcessor.cs @@ -29,30 +29,21 @@ static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAsse ScriptableObject collectionItem = AssetDatabase.LoadAssetAtPath(importedAssetPath); - if (collectionItem != null) + if (collectionItem == null) { - if (collectionItem is ISOCItem socItem) - { - if (socItem.Collection == null) - { - continue; - } + continue; + } + + if (collectionItem is not ISOCItem socItem) + { + continue; + } - if (!socItem.Collection.Contains(collectionItem)) - { - if (socItem.Collection.TryGetItemByGUID(socItem.GUID, out _)) - { - Debug.LogWarning( - $"Collection already contains one item with the same GUID" + - $" ({socItem.GUID}) but different name ({socItem.name}), generating new GUID"); - socItem.GenerateNewGUID(); - } - - socItem.Collection.Add(collectionItem); - Debug.Log($"{collectionItem.name} has collection assigned " - + $"{socItem.Collection} but its missing from collection list, adding it"); - } - } + if (!CollectionsRegistry.Instance.HasUniqueGUID(socItem)) + { + socItem.GenerateNewGUID(); + socItem.ClearCollection(); + Debug.LogWarning($"Item {socItem} GUID was not unique, generating a new one and clearing the collection"); } } @@ -64,6 +55,13 @@ static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAsse if (collection == null) continue; + if (!CollectionsRegistry.Instance.HasUniqueGUID(collection)) + { + collection.GenerateNewGUID(); + collection.Clear(); + Debug.LogWarning($"Collection {collection} GUID was not unique, generating a new one, and clearing the items"); + } + if (!CollectionsRegistry.Instance.IsKnowCollection(collection)) { RefreshRegistry(); @@ -95,4 +93,4 @@ static void OnAfterScriptsReloading() RefreshRegistryAfterRecompilation = false; } } -} +} \ No newline at end of file diff --git a/Scripts/Editor/PropertyDrawers/CollectionReferenceLongGuidDrawer.cs b/Scripts/Editor/PropertyDrawers/CollectionReferenceLongGuidDrawer.cs new file mode 100644 index 0000000..4d3d85a --- /dev/null +++ b/Scripts/Editor/PropertyDrawers/CollectionReferenceLongGuidDrawer.cs @@ -0,0 +1,29 @@ +using UnityEditor; +using UnityEngine; + +namespace BrunoMikoski.ScriptableObjectCollections.Picker +{ + [CustomPropertyDrawer(typeof(CollectionReferenceLongGuidAttribute))] + public class CollectionReferenceLongGuidDrawer : PropertyDrawer + { + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) + { + return EditorGUIUtility.singleLineHeight; + } + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + LongGuid collectionGUID = (LongGuid)property.boxedValue; + + ScriptableObjectCollection collection = null; + if (collectionGUID.IsValid()) + { + collection = CollectionsRegistry.Instance.GetCollectionByGUID(collectionGUID); + } + + EditorGUI.BeginDisabledGroup(true); + EditorGUI.ObjectField(position, "Collection", collection, typeof(ScriptableObjectCollection), false); + EditorGUI.EndDisabledGroup(); + } + } +} \ No newline at end of file diff --git a/Scripts/Editor/PropertyDrawers/CollectionReferenceLongGuidDrawer.cs.meta b/Scripts/Editor/PropertyDrawers/CollectionReferenceLongGuidDrawer.cs.meta new file mode 100644 index 0000000..4bb0103 --- /dev/null +++ b/Scripts/Editor/PropertyDrawers/CollectionReferenceLongGuidDrawer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ba509ba6e52c4bb3ba782d70edda061b +timeCreated: 1739954642 \ No newline at end of file diff --git a/Scripts/Editor/Utils/SOCollectionsContextMenuItems.cs b/Scripts/Editor/Utils/SOCollectionsContextMenuItems.cs index 58ced08..fd347c9 100644 --- a/Scripts/Editor/Utils/SOCollectionsContextMenuItems.cs +++ b/Scripts/Editor/Utils/SOCollectionsContextMenuItems.cs @@ -6,77 +6,75 @@ namespace BrunoMikoski.ScriptableObjectCollections { public static class SOCollectionsProjectContextMenus { - // ================================ - // ISOCItem (ScriptableObjectCollectionItem) commands - // ================================ - - [MenuItem("Assets/ScriptableObjectCollections/ISOCItem/Move to Different Collection", true)] + [MenuItem("Assets/Move to Different Collection", true, priority = 10000)] private static bool ValidateMoveToDifferentCollection() { Object[] selectedObjects = Selection.objects; if (selectedObjects == null || selectedObjects.Length == 0) return false; - // Check that every selected object implements ISOCItem. + foreach (Object obj in selectedObjects) { ISOCItem socItem = obj as ISOCItem; if (socItem == null) return false; } + + List possibleCollections = + CollectionsRegistry.Instance.GetCollectionsByItemType(selectedObjects[0].GetType()); + + if (possibleCollections == null || possibleCollections.Count <= 1) + { + return false; + } + return true; } - [MenuItem("Assets/ScriptableObjectCollections/ISOCItem/Move to Different Collection")] + [MenuItem("Assets/Move to Different Collection", priority = 10000)] private static void MoveToDifferentCollection() { Object[] selectedObjects = Selection.objects; List items = new List(); + foreach (Object obj in selectedObjects) { if (obj is ISOCItem item) items.Add(item); } + if (items.Count == 0) return; - // Get available collections for the item type. List possibleCollections = CollectionsRegistry.Instance.GetCollectionsByItemType(items[0].GetType()); + if (possibleCollections == null || possibleCollections.Count == 0) { EditorUtility.DisplayDialog("Move to Different Collection", "No collections available.", "OK"); return; } - // Exclude the current collection of the first item. + ScriptableObjectCollection currentCollection = items[0].Collection; + List filteredCollections = new List(); foreach (ScriptableObjectCollection collection in possibleCollections) { if (collection != currentCollection) filteredCollections.Add(collection); } + if (filteredCollections.Count == 0) { EditorUtility.DisplayDialog("Move to Different Collection", "No other collections available.", "OK"); return; } - // Present a GenericMenu so the user can choose a new collection. - GenericMenu menu = new GenericMenu(); - foreach (ScriptableObjectCollection collection in filteredCollections) - { - menu.AddItem(new GUIContent(collection.name), false, delegate - { - foreach (ISOCItem item in items) - { - SOCItemUtility.MoveItem(item, collection); - } - EditorUtility.SetDirty(collection); - }); - } - menu.ShowAsContext(); + + MoveToCollectionWindow.ShowWindow(items, filteredCollections); } - [MenuItem("Assets/ScriptableObjectCollections/ISOCItem/Select Collection", true)] + + [MenuItem("Assets/Select Collection", true, priority = 10000)] private static bool ValidateSelectCollection() { Object[] selectedObjects = Selection.objects; @@ -86,7 +84,7 @@ private static bool ValidateSelectCollection() return socItem != null && socItem.Collection != null; } - [MenuItem("Assets/ScriptableObjectCollections/ISOCItem/Select Collection")] + [MenuItem("Assets/Select Collection", priority = 10000)] private static void SelectCollection() { Object[] selectedObjects = Selection.objects; @@ -96,117 +94,5 @@ private static void SelectCollection() if (socItem != null && socItem.Collection != null) Selection.activeObject = socItem.Collection; } - - [MenuItem("Assets/ScriptableObjectCollections/ISOCItem/Duplicate", true)] - private static bool ValidateDuplicateItem() - { - Object[] selectedObjects = Selection.objects; - if (selectedObjects == null || selectedObjects.Length == 0) - return false; - foreach (Object obj in selectedObjects) - { - if (!(obj is ScriptableObject)) - return false; - } - return true; - } - - [MenuItem("Assets/ScriptableObjectCollections/ISOCItem/Duplicate")] - private static void DuplicateItem() - { - Object[] selectedObjects = Selection.objects; - foreach (Object obj in selectedObjects) - { - string assetPath = AssetDatabase.GetAssetPath(obj); - if (string.IsNullOrEmpty(assetPath)) - continue; - string newPath = AssetDatabase.GenerateUniqueAssetPath(assetPath); - bool copySuccess = AssetDatabase.CopyAsset(assetPath, newPath); - if (copySuccess) - AssetDatabase.Refresh(); - } - } - - [MenuItem("Assets/ScriptableObjectCollections/ISOCItem/Delete", true)] - private static bool ValidateDeleteItem() - { - Object[] selectedObjects = Selection.objects; - if (selectedObjects == null || selectedObjects.Length == 0) - return false; - foreach (Object obj in selectedObjects) - { - if (!(obj is ScriptableObject)) - return false; - } - return true; - } - - [MenuItem("Assets/ScriptableObjectCollections/ISOCItem/Delete")] - private static void DeleteItem() - { - Object[] selectedObjects = Selection.objects; - if (EditorUtility.DisplayDialog("Delete Item", - "Are you sure you want to delete the selected item(s)?", "Yes", "No")) - { - List objectCollections = new List(); - foreach (Object obj in selectedObjects) - { - string assetPath = AssetDatabase.GetAssetPath(obj); - if(obj is ISOCItem socItem) - objectCollections.Add(socItem.Collection); - - if (!string.IsNullOrEmpty(assetPath)) - AssetDatabase.DeleteAsset(assetPath); - } - - foreach (ScriptableObjectCollection objectCollection in objectCollections) - { - objectCollection.RefreshCollection(); - } - - AssetDatabase.Refresh(); - } - } - - // ================================ - // ScriptableObjectCollection commands - // ================================ - - [MenuItem("Assets/ScriptableObjectCollections/ScriptableObjectCollection/Duplicate Collection", true)] - private static bool ValidateDuplicateCollection() - { - Object[] selectedObjects = Selection.objects; - if (selectedObjects == null || selectedObjects.Length != 1) - return false; - return selectedObjects[0] is ScriptableObjectCollection; - } - - [MenuItem("Assets/ScriptableObjectCollections/ScriptableObjectCollection/Duplicate Collection")] - private static void DuplicateCollection() - { - Object[] selectedObjects = Selection.objects; - if (selectedObjects == null || selectedObjects.Length != 1) - return; - ScriptableObjectCollection originalCollection = selectedObjects[0] as ScriptableObjectCollection; - if (originalCollection == null) - return; - string assetPath = AssetDatabase.GetAssetPath(originalCollection); - if (string.IsNullOrEmpty(assetPath)) - return; - string newPath = AssetDatabase.GenerateUniqueAssetPath(assetPath); - bool copySuccess = AssetDatabase.CopyAsset(assetPath, newPath); - if (copySuccess) - { - AssetDatabase.Refresh(); - CollectionsRegistry.Instance.ValidateCollections(); - EditorUtility.DisplayDialog("Duplicate Collection", - "Collection duplicated successfully at:\n" + newPath, "OK"); - } - else - { - EditorUtility.DisplayDialog("Duplicate Collection", - "Failed to duplicate collection.", "OK"); - } - } } } \ No newline at end of file diff --git a/Scripts/Runtime/Attributes/CollectionReferenceLongGuidAttribute.cs b/Scripts/Runtime/Attributes/CollectionReferenceLongGuidAttribute.cs new file mode 100644 index 0000000..a459f39 --- /dev/null +++ b/Scripts/Runtime/Attributes/CollectionReferenceLongGuidAttribute.cs @@ -0,0 +1,11 @@ +using System; +using UnityEngine; + +namespace BrunoMikoski.ScriptableObjectCollections +{ + [AttributeUsage(AttributeTargets.Field)] + public class CollectionReferenceLongGuidAttribute : PropertyAttribute + { + + } +} \ No newline at end of file diff --git a/Scripts/Runtime/Attributes/CollectionReferenceLongGuidAttribute.cs.meta b/Scripts/Runtime/Attributes/CollectionReferenceLongGuidAttribute.cs.meta new file mode 100644 index 0000000..f2a9c98 --- /dev/null +++ b/Scripts/Runtime/Attributes/CollectionReferenceLongGuidAttribute.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 60387b23f75f40a4b2b5e46fdee24397 +timeCreated: 1739954588 \ No newline at end of file diff --git a/Scripts/Runtime/Attributes/SOCItemEditorOptionsAttribute.cs b/Scripts/Runtime/Attributes/SOCItemEditorOptionsAttribute.cs index e52cbac..b58ae7e 100644 --- a/Scripts/Runtime/Attributes/SOCItemEditorOptionsAttribute.cs +++ b/Scripts/Runtime/Attributes/SOCItemEditorOptionsAttribute.cs @@ -47,4 +47,4 @@ public class SOCItemEditorOptionsAttribute : Attribute public class CollectionItemEditorOptions : SOCItemEditorOptionsAttribute { } -} +} \ No newline at end of file diff --git a/Scripts/Runtime/Core/CollectionsRegistry.cs b/Scripts/Runtime/Core/CollectionsRegistry.cs index d4492ea..feefe36 100644 --- a/Scripts/Runtime/Core/CollectionsRegistry.cs +++ b/Scripts/Runtime/Core/CollectionsRegistry.cs @@ -522,5 +522,35 @@ public void UpdateAutoSearchForCollections() SetAutoSearchForCollections(false); } + + public bool HasUniqueGUID(ISOCItem targetItem) + { + for (int i = 0; i < collections.Count; i++) + { + ScriptableObjectCollection collection = collections[i]; + foreach (ScriptableObject scriptableObject in collection) + { + if (scriptableObject is ISOCItem socItem) + { + if(!Equals(socItem, targetItem) && socItem.GUID == targetItem.GUID) + return false; + } + } + } + + return true; + } + + public bool HasUniqueGUID(ScriptableObjectCollection targetCollection) + { + for (int i = 0; i < collections.Count; i++) + { + ScriptableObjectCollection collection = collections[i]; + if (collection != targetCollection && collection.GUID == targetCollection.GUID) + return false; + } + + return true; + } } } \ No newline at end of file diff --git a/Scripts/Runtime/Core/ScriptableObjectCollection.cs b/Scripts/Runtime/Core/ScriptableObjectCollection.cs index 3191518..519cf4c 100644 --- a/Scripts/Runtime/Core/ScriptableObjectCollection.cs +++ b/Scripts/Runtime/Core/ScriptableObjectCollection.cs @@ -111,7 +111,7 @@ public bool Add(ScriptableObject item) return true; } - internal void GenerateNewGUID() + public void GenerateNewGUID() { guid = LongGuid.NewGuid(); ObjectUtility.SetDirty(this); @@ -331,7 +331,6 @@ public void RefreshCollection() string[] guids = AssetDatabase.FindAssets($"t:{collectionItemType.Name}", new []{folder}); List itemsFromOtherCollections = new List(); - List itemsMissingCollection = new List(); for (int i = 0; i < guids.Length; i++) { ScriptableObject item = @@ -354,10 +353,6 @@ public void RefreshCollection() if (socItem.Collection.Contains(item)) continue; } - else - { - itemsMissingCollection.Add(socItem); - } if (Add(item)) changed = true; @@ -375,6 +370,18 @@ public void RefreshCollection() } ScriptableObject scriptableObject = items[i]; + + + if (scriptableObject is ISOCItem socItem) + { + if (socItem.Collection != this) + { + RemoveAt(i); + Debug.Log($"Removing item at index {i} since it belongs to another collection {socItem.Collection}"); + changed = true; + } + } + if (scriptableObject.GetType() == GetItemType() || scriptableObject.GetType().IsSubclassOf(GetItemType())) continue; @@ -393,13 +400,31 @@ public void RefreshCollection() foreach (ISOCItem itemsFromOtherCollection in itemsFromOtherCollections) { SOCItemUtility.MoveItem(itemsFromOtherCollection, itemsFromOtherCollection.Collection); + changed = true; + ObjectUtility.SetDirty(itemsFromOtherCollection.Collection); } + } else if (result == 1) { + if (!CollectionsRegistry.Instance.HasUniqueGUID(this)) + { + GenerateNewGUID(); + Clear(); + } + + if (!CollectionsRegistry.Instance.IsKnowCollection(this)) + { + CollectionsRegistry.Instance.RegisterCollection(this); + } + foreach (ISOCItem itemsFromOtherCollection in itemsFromOtherCollections) { - SOCItemUtility.MoveItem(itemsFromOtherCollection, this); + itemsFromOtherCollection.ClearCollection(); + Add(itemsFromOtherCollection as ScriptableObject); + ObjectUtility.SetDirty(itemsFromOtherCollection as ScriptableObject); + changed = true; + } } } diff --git a/Scripts/Runtime/Core/ScriptableObjectCollectionItem.cs b/Scripts/Runtime/Core/ScriptableObjectCollectionItem.cs index 4919883..5950dae 100644 --- a/Scripts/Runtime/Core/ScriptableObjectCollectionItem.cs +++ b/Scripts/Runtime/Core/ScriptableObjectCollectionItem.cs @@ -19,7 +19,7 @@ public LongGuid GUID } } - [SerializeField, HideInInspector] + [SerializeField, CollectionReferenceLongGuid] private LongGuid collectionGUID; From cf816b860f9176f4e995176ac291ffa371eccc73 Mon Sep 17 00:00:00 2001 From: Bruno Mikoski Date: Wed, 19 Feb 2025 09:23:06 +0000 Subject: [PATCH 4/4] fix: meta files --- CHANGELOG.MD | 14 ++++++++++++++ package.json | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 10964e5..17311f2 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -5,6 +5,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [2.3.9] - 19/02/2025 +## Changed + - Added some general fixes to validate Collection and Items have unique `LongGuid` + - Fixed issues when after some operation with the context menu on the CollectionEditor items would not refresh properly + - Fixed issue when moving items between collection would not generate unique names + - Removed the post processing of assets that tried to assign items to collections automatically, since this was causing issue when duplicating collections, everyting now is done on the collection + - Overall code improvement and organization + +## Added + - Added new Context Menu for moving items between collections + - Added the new PropertyDrawer that shows what Collection every item belongs to + - Added a few more Confirmation dialog when detecting items that looks wrong + ## [2.3.8] - 30/01/2025 ## Changed @@ -612,6 +625,7 @@ public bool IsValidConsumable(Consumable consumable) ### Added - First initial working version +[2.3.9]: https://github.com/badawe/ScriptableObjectCollection/releases/tag/v2.3.9 [2.3.8]: https://github.com/badawe/ScriptableObjectCollection/releases/tag/v2.3.8 [2.3.7]: https://github.com/badawe/ScriptableObjectCollection/releases/tag/v2.3.7 [2.3.6]: https://github.com/badawe/ScriptableObjectCollection/releases/tag/v2.3.6 diff --git a/package.json b/package.json index f385dc2..425ebb7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "com.brunomikoski.scriptableobjectcollection", "displayName": "Scriptable Object Collection", - "version": "2.3.8", + "version": "2.3.9", "unity": "2022.2", "description": "A library to help improve the usability of Unity3D Scriptable Objects by grouping them into a collection and exposing them by code or nice inspectors!", "keywords": [