From ceab1fab6671c20baebfd0e06377843e27ea411b Mon Sep 17 00:00:00 2001 From: yellowpapaper Date: Tue, 14 Mar 2023 20:13:23 +0100 Subject: [PATCH 01/14] fixed issues with certain null object values --- .../Scripts/SerializedCollectionsUtility.cs | 2 +- Runtime/Scripts/SerializedDictionary.cs | 2 +- Sample/SerializedDictionarySample.unity | 52 +++++++++++++++++++ Sample/SerializedDictionarySampleThree.cs | 12 +++++ .../SerializedDictionarySampleThree.cs.meta | 11 ++++ 5 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 Sample/SerializedDictionarySampleThree.cs create mode 100644 Sample/SerializedDictionarySampleThree.cs.meta diff --git a/Runtime/Scripts/SerializedCollectionsUtility.cs b/Runtime/Scripts/SerializedCollectionsUtility.cs index a612562..ee9fddf 100644 --- a/Runtime/Scripts/SerializedCollectionsUtility.cs +++ b/Runtime/Scripts/SerializedCollectionsUtility.cs @@ -11,7 +11,7 @@ public static bool IsValidKey(object obj) // we catch this error if we are not on the main thread and simply return false as we assume the object is null try { - return !(obj is Object unityObject && unityObject == null); + return !(obj == null || (obj is Object unityObject && unityObject == null)); } catch { diff --git a/Runtime/Scripts/SerializedDictionary.cs b/Runtime/Scripts/SerializedDictionary.cs index 3af95db..0655a88 100644 --- a/Runtime/Scripts/SerializedDictionary.cs +++ b/Runtime/Scripts/SerializedDictionary.cs @@ -31,7 +31,7 @@ public void OnAfterDeserialize() foreach (var kvp in _serializedList) { #if UNITY_EDITOR - if (!ContainsKey(kvp.Key)) + if (SerializedCollectionsUtility.IsValidKey(kvp.Key) && !ContainsKey(kvp.Key)) Add(kvp.Key, kvp.Value); #else Add(kvp.Key, kvp.Value); diff --git a/Sample/SerializedDictionarySample.unity b/Sample/SerializedDictionarySample.unity index 4cb2419..dbc6053 100644 --- a/Sample/SerializedDictionarySample.unity +++ b/Sample/SerializedDictionarySample.unity @@ -209,6 +209,58 @@ Transform: m_Father: {fileID: 0} m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1275956276 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1275956278} + - component: {fileID: 1275956279} + m_Layer: 0 + m_Name: UnityObjectKeys + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1275956278 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1275956276} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1275956279 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1275956276} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 62752a297e2d2104eb3704071566e844, type: 3} + m_Name: + m_EditorClassIdentifier: + _nameOverrides: + _serializedList: + - Key: {fileID: 0} + Value: + - Key: {fileID: 0} + Value: + - Key: {fileID: 0} + Value: --- !u!4 &3293770536253637320 Transform: m_ObjectHideFlags: 0 diff --git a/Sample/SerializedDictionarySampleThree.cs b/Sample/SerializedDictionarySampleThree.cs new file mode 100644 index 0000000..f89d1d1 --- /dev/null +++ b/Sample/SerializedDictionarySampleThree.cs @@ -0,0 +1,12 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace AYellowpaper.SerializedCollections +{ + public class SerializedDictionarySampleThree : MonoBehaviour + { + [SerializeField] + private SerializedDictionary _nameOverrides; + } +} \ No newline at end of file diff --git a/Sample/SerializedDictionarySampleThree.cs.meta b/Sample/SerializedDictionarySampleThree.cs.meta new file mode 100644 index 0000000..5afe49a --- /dev/null +++ b/Sample/SerializedDictionarySampleThree.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 62752a297e2d2104eb3704071566e844 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 077396368fdc668b28a0eada5cc9de9e56b29d0c Mon Sep 17 00:00:00 2001 From: yellowpapaper Date: Wed, 22 Mar 2023 00:14:38 +0100 Subject: [PATCH 02/14] Fix: prefabs would lose their dictionary data --- Runtime/Scripts/SerializedDictionary.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Runtime/Scripts/SerializedDictionary.cs b/Runtime/Scripts/SerializedDictionary.cs index 0655a88..ede36bc 100644 --- a/Runtime/Scripts/SerializedDictionary.cs +++ b/Runtime/Scripts/SerializedDictionary.cs @@ -50,6 +50,9 @@ public void OnBeforeSerialize() #if UNITY_EDITOR if (UnityEditor.BuildPipeline.isBuildingPlayer) LookupTable.RemoveDuplicates(); +#else + foreach (var kvp in this) + _serializedList.Add(new SerializedKeyValuePair(kvp.Key, kvp.Value)); #endif } } From ad15bf8054aabd42e9eed3f31108e34cb7627eda Mon Sep 17 00:00:00 2001 From: yellowpapaper Date: Wed, 22 Mar 2023 19:24:30 +0100 Subject: [PATCH 03/14] support serialization when using .Add method --- Runtime/Scripts/SerializedDictionary.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Runtime/Scripts/SerializedDictionary.cs b/Runtime/Scripts/SerializedDictionary.cs index ede36bc..1d63395 100644 --- a/Runtime/Scripts/SerializedDictionary.cs +++ b/Runtime/Scripts/SerializedDictionary.cs @@ -24,6 +24,24 @@ internal IKeyable LookupTable private DictionaryLookupTable _lookupTable; #endif +#if UNITY_EDITOR + public new void Add(TKey key, TValue value) + { + base.Add(key, value); + _serializedList.Add(new SerializedKeyValuePair(key, value)); + } + + /// + /// Only available in Editor. Add a key value pair, even if the key already exists in the dictionary. + /// + public void AddConflictAllowed(TKey key, TValue value) + { + if (!ContainsKey(key)) + base.Add(key, value); + _serializedList.Add(new SerializedKeyValuePair(key, value)); + } +#endif + public void OnAfterDeserialize() { Clear(); @@ -32,7 +50,7 @@ public void OnAfterDeserialize() { #if UNITY_EDITOR if (SerializedCollectionsUtility.IsValidKey(kvp.Key) && !ContainsKey(kvp.Key)) - Add(kvp.Key, kvp.Value); + base.Add(kvp.Key, kvp.Value); #else Add(kvp.Key, kvp.Value); #endif From feaa40f60b61e15d3f1812ed133c92fd2b850453 Mon Sep 17 00:00:00 2001 From: yellowpapaper Date: Thu, 30 Mar 2023 23:08:00 +0200 Subject: [PATCH 04/14] added all constructors that were missing, including one for copying a serialized dictionary. Also added Clear, Remove and TryAdd --- .../SerializedDictionaryInstanceDrawer.cs | 45 ++++++----- Runtime/LookupTables/DictionaryLookupTable.cs | 6 ++ Runtime/LookupTables/IKeyable.cs | 1 + Runtime/Scripts/SerializedDictionary.cs | 76 ++++++++++++++++++- 4 files changed, 108 insertions(+), 20 deletions(-) diff --git a/Editor/Scripts/SerializedDictionaryInstanceDrawer.cs b/Editor/Scripts/SerializedDictionaryInstanceDrawer.cs index 2656453..2f80d8a 100644 --- a/Editor/Scripts/SerializedDictionaryInstanceDrawer.cs +++ b/Editor/Scripts/SerializedDictionaryInstanceDrawer.cs @@ -156,12 +156,24 @@ private void UpdateAfterInput() { InitializeSettingsIfNeeded(); ProcessState(); + CheckIfNewDictionary(); CheckPaging(); var elementsPerPage = EditorUserSettings.Get().ElementsPerPage; int pageCount = Mathf.Max(1, Mathf.CeilToInt((float)DefaultState.ListSize / elementsPerPage)); ToggleSearchBar(_propertyData.AlwaysShowSearch ? true : SCEditorUtility.ShouldShowSearch(pageCount)); } + // TODO: This works for now, but isn't perfect. This checks if the serialized dictionary was reassigned with new(), simply by comparing the count. Should be instead done by reference equality in the future + private void CheckIfNewDictionary() + { + if (_singleEditingData.IsValid && _singleEditingData.LookupTable.GetCount() != _activeState.ListSize) + { + var dictionary = SCEditorUtility.GetPropertyValue(ListProperty, ListProperty.serializedObject.targetObject); + _singleEditingData.LookupTable = GetLookupTable(dictionary); + _singleEditingData.LookupTable.RecalculateOccurences(); + } + } + private void InitializeSettingsIfNeeded() { void InitializeSettings(bool fieldFlag) @@ -253,7 +265,7 @@ private ReorderableList MakeList() private ReorderableList MakeUnexpandedList() { var list = new ReorderableList(SerializedDictionaryDrawer.NoEntriesList, typeof(int)); - list.drawHeaderCallback = DrawUnexpandedHeader; + list.drawHeaderCallback = OnDrawUnexpandedHeader; return list; } @@ -288,18 +300,6 @@ private void OnDrawNoneElement(Rect rect) return (displayType, canToggleListDrawer); } - private void DrawUnexpandedHeader(Rect rect) - { - EditorGUI.BeginProperty(rect, _label, ListProperty); - ListProperty.isExpanded = EditorGUI.Foldout(rect.WithX(rect.x - 5), ListProperty.isExpanded, _label, true); - - var detailsStyle = EditorStyles.miniLabel; - var detailsRect = rect.AppendRight(0).AppendLeft(detailsStyle.CalcSize(_shortDetailsContent).x); - GUI.Label(detailsRect, _shortDetailsContent, detailsStyle); - - EditorGUI.EndProperty(); - } - private void DoPaging(Rect rect) { EditorGUI.BeginChangeCheck(); @@ -326,6 +326,20 @@ private void OnDrawHeader(Rect rect) UpdateAfterInput(); } + + private void OnDrawUnexpandedHeader(Rect rect) + { + EditorGUI.BeginProperty(rect, _label, ListProperty); + ListProperty.isExpanded = EditorGUI.Foldout(rect.WithX(rect.x - 5), ListProperty.isExpanded, _label, true); + + var detailsStyle = EditorStyles.miniLabel; + var detailsRect = rect.AppendRight(0).AppendLeft(detailsStyle.CalcSize(_shortDetailsContent).x); + GUI.Label(detailsRect, _shortDetailsContent, detailsStyle); + + EditorGUI.EndProperty(); + + UpdateAfterInput(); + } private void DoMainHeader(Rect rect) { @@ -673,11 +687,6 @@ private void OnRemove(ReorderableList list) { _activeState.RemoveElementAt(_pagedIndices[list.index]); UpdatePaging(); - //int actualIndex = _pagedIndices[list.index]; - //ListProperty.DeleteArrayElementAtIndex(actualIndex); - //UpdatePaging(); - //if (actualIndex >= ListProperty.minArraySize) - // list.index = _pagedIndices.Count - 1; } } } \ No newline at end of file diff --git a/Runtime/LookupTables/DictionaryLookupTable.cs b/Runtime/LookupTables/DictionaryLookupTable.cs index 0f9fab8..4bb43e6 100644 --- a/Runtime/LookupTables/DictionaryLookupTable.cs +++ b/Runtime/LookupTables/DictionaryLookupTable.cs @@ -1,6 +1,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using UnityEngine; namespace AYellowpaper.SerializedCollections { @@ -64,6 +65,11 @@ public object GetKeyAt(int index) return _dictionary._serializedList[index]; } + public int GetCount() + { + return _dictionary._serializedList.Count; + } + public void RemoveDuplicates() { _dictionary._serializedList = _dictionary._serializedList diff --git a/Runtime/LookupTables/IKeyable.cs b/Runtime/LookupTables/IKeyable.cs index 936f076..a0f0791 100644 --- a/Runtime/LookupTables/IKeyable.cs +++ b/Runtime/LookupTables/IKeyable.cs @@ -14,6 +14,7 @@ internal interface IKeyable void RemoveKey(object key); void RemoveAt(int index); object GetKeyAt(int index); + int GetCount(); void RemoveDuplicates(); } } diff --git a/Runtime/Scripts/SerializedDictionary.cs b/Runtime/Scripts/SerializedDictionary.cs index 1d63395..8de88fd 100644 --- a/Runtime/Scripts/SerializedDictionary.cs +++ b/Runtime/Scripts/SerializedDictionary.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using UnityEngine; @@ -9,7 +10,7 @@ public partial class SerializedDictionary : Dictionary> _serializedList = new List>(); - + #if UNITY_EDITOR internal IKeyable LookupTable { @@ -23,6 +24,48 @@ internal IKeyable LookupTable private DictionaryLookupTable _lookupTable; #endif + + public SerializedDictionary() : base() {} + + public SerializedDictionary(SerializedDictionary serializedDictionary) : base(serializedDictionary) + { +#if UNITY_EDITOR + foreach (var kvp in serializedDictionary._serializedList) + _serializedList.Add(new SerializedKeyValuePair(kvp.Key, kvp.Value)); +#endif + } + + public SerializedDictionary(IDictionary dictionary) : base(dictionary) + { + SyncDictionaryToBackingField_Editor(); + } + + public SerializedDictionary(IDictionary dictionary, IEqualityComparer comparer) : base( + dictionary, comparer) + { + SyncDictionaryToBackingField_Editor(); + } + + public SerializedDictionary(IEnumerable> collection) : base(collection) + { + SyncDictionaryToBackingField_Editor(); + } + + public SerializedDictionary(IEnumerable> collection, + IEqualityComparer comparer) : base(collection, comparer) + { + SyncDictionaryToBackingField_Editor(); + } + public SerializedDictionary(IEqualityComparer comparer) : base(comparer) { } + public SerializedDictionary(int capacity) : base(capacity) { } + public SerializedDictionary(int capacity, IEqualityComparer comparer) : base(capacity, comparer) { } + + [Conditional("UNITY_EDITOR")] + private void SyncDictionaryToBackingField_Editor() + { + foreach (var kvp in this) + _serializedList.Add(new SerializedKeyValuePair(kvp.Key, kvp.Value)); + } #if UNITY_EDITOR public new void Add(TKey key, TValue value) @@ -31,6 +74,35 @@ internal IKeyable LookupTable _serializedList.Add(new SerializedKeyValuePair(key, value)); } + public new void Clear() + { + base.Clear(); + _serializedList.Clear(); + } + + public new bool Remove(TKey key) + { + if (TryGetValue(key, out var value)) + { + base.Remove(key); + _serializedList.Remove(new SerializedKeyValuePair(key, value)); + return true; + } + + return false; + } + + public new bool TryAdd(TKey key, TValue value) + { + if (base.TryAdd(key, value)) + { + _serializedList.Add(new SerializedKeyValuePair(key, value)); + return true; + } + + return false; + } + /// /// Only available in Editor. Add a key value pair, even if the key already exists in the dictionary. /// @@ -44,7 +116,7 @@ public void AddConflictAllowed(TKey key, TValue value) public void OnAfterDeserialize() { - Clear(); + base.Clear(); foreach (var kvp in _serializedList) { From ccfc634249a71d59943c18e6b1630c1865d26bd7 Mon Sep 17 00:00:00 2001 From: yellowpapaper Date: Sat, 1 Apr 2023 12:36:50 +0200 Subject: [PATCH 05/14] made sure the drawer is always considered first when using Odin. Thanks Odin for the priority attribute! --- Editor/Scripts/SerializedDictionaryDrawer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Editor/Scripts/SerializedDictionaryDrawer.cs b/Editor/Scripts/SerializedDictionaryDrawer.cs index afc3980..f66aa4a 100644 --- a/Editor/Scripts/SerializedDictionaryDrawer.cs +++ b/Editor/Scripts/SerializedDictionaryDrawer.cs @@ -13,6 +13,9 @@ namespace AYellowpaper.SerializedCollections.Editor { +#if ODIN_INSPECTOR + [Sirenix.OdinInspector.Editor.DrawerPriority(Sirenix.OdinInspector.Editor.DrawerPriorityLevel.SuperPriority)] +#endif [CustomPropertyDrawer(typeof(SerializedDictionary<,>))] public class SerializedDictionaryDrawer : PropertyDrawer { From 33beacc62858cd8b9b9e1fc704e07c9371fba9b1 Mon Sep 17 00:00:00 2001 From: yellowpapaper Date: Sat, 1 Apr 2023 13:14:04 +0200 Subject: [PATCH 06/14] light cleanup and added support for dictionary properly updating in editor when being set --- Editor/Scripts/Utility/SCEditorUtility.cs | 20 ------------------- Runtime/LookupTables/DictionaryLookupTable.cs | 2 +- .../Scripts/SerializedCollectionsUtility.cs | 5 +++++ Runtime/Scripts/SerializedDictionary.cs | 20 ++++++++++++++++++- 4 files changed, 25 insertions(+), 22 deletions(-) diff --git a/Editor/Scripts/Utility/SCEditorUtility.cs b/Editor/Scripts/Utility/SCEditorUtility.cs index 2d488ca..395d824 100644 --- a/Editor/Scripts/Utility/SCEditorUtility.cs +++ b/Editor/Scripts/Utility/SCEditorUtility.cs @@ -15,21 +15,6 @@ internal static class SCEditorUtility public const bool KeyFlag = true; public const bool ValueFlag = false; - public static bool GetPersistentBool(string path, bool defaultValue) - { - return EditorPrefs.GetBool(EditorPrefsPrefix + path, defaultValue); - } - - public static bool HasKey(string path) - { - return EditorPrefs.HasKey( EditorPrefsPrefix + path ); - } - - public static void SetPersistentBool(string path, bool value) - { - EditorPrefs.SetBool(EditorPrefsPrefix + path, value); - } - public static float CalculateHeight(SerializedProperty property, DisplayType displayType) { return CalculateHeight(property, displayType == DisplayType.List ? true : false); @@ -64,11 +49,6 @@ public static IEnumerable GetChildren(SerializedProperty pro } while (property.NextVisible(recursive) && !SerializedProperty.EqualContents(property, end)); } - public static int GetActualArraySize(SerializedProperty arrayProperty) - { - return GetChildren(arrayProperty).Count() - 1; - } - public static PropertyData GetPropertyData(SerializedProperty property) { var data = new PropertyData(); diff --git a/Runtime/LookupTables/DictionaryLookupTable.cs b/Runtime/LookupTables/DictionaryLookupTable.cs index 4bb43e6..cb85b39 100644 --- a/Runtime/LookupTables/DictionaryLookupTable.cs +++ b/Runtime/LookupTables/DictionaryLookupTable.cs @@ -50,7 +50,7 @@ public void RemoveKey(object key) for (int i = _dictionary._serializedList.Count - 1; i >= 0; i--) { var dictKey = _dictionary._serializedList[i].Key; - if ((object)dictKey == key || dictKey.Equals(key)) + if (SerializedCollectionsUtility.KeysAreEqual(dictKey, key)) _dictionary._serializedList.RemoveAt(i); } } diff --git a/Runtime/Scripts/SerializedCollectionsUtility.cs b/Runtime/Scripts/SerializedCollectionsUtility.cs index ee9fddf..f3f7b29 100644 --- a/Runtime/Scripts/SerializedCollectionsUtility.cs +++ b/Runtime/Scripts/SerializedCollectionsUtility.cs @@ -18,5 +18,10 @@ public static bool IsValidKey(object obj) return false; } } + + public static bool KeysAreEqual(T key, object otherKey) + { + return (object)key == otherKey || key.Equals(otherKey); + } } } \ No newline at end of file diff --git a/Runtime/Scripts/SerializedDictionary.cs b/Runtime/Scripts/SerializedDictionary.cs index 8de88fd..8f28338 100644 --- a/Runtime/Scripts/SerializedDictionary.cs +++ b/Runtime/Scripts/SerializedDictionary.cs @@ -6,7 +6,7 @@ namespace AYellowpaper.SerializedCollections { [System.Serializable] - public partial class SerializedDictionary : Dictionary, ISerializationCallbackReceiver + public class SerializedDictionary : Dictionary, ISerializationCallbackReceiver { [SerializeField] internal List> _serializedList = new List>(); @@ -68,6 +68,24 @@ private void SyncDictionaryToBackingField_Editor() } #if UNITY_EDITOR + public new TValue this[TKey key] + { + get => base[key]; + set + { + for (int i = 0; i < _serializedList.Count; i++) + { + var kvp = _serializedList[i]; + if (!SerializedCollectionsUtility.KeysAreEqual(key, kvp.Key)) + continue; + kvp.Value = value; + _serializedList[i] = kvp; + } + + base[key] = value; + } + } + public new void Add(TKey key, TValue value) { base.Add(key, value); From 309877a829c3505368d5f06a2b47ec5b97d1488f Mon Sep 17 00:00:00 2001 From: yellowpapaper Date: Sat, 1 Apr 2023 13:16:21 +0200 Subject: [PATCH 07/14] made sure setting they base key comes first, so an error is thrown when the key doesn't exist at the earliest time possible --- Runtime/Scripts/SerializedDictionary.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Runtime/Scripts/SerializedDictionary.cs b/Runtime/Scripts/SerializedDictionary.cs index 8f28338..ba5328b 100644 --- a/Runtime/Scripts/SerializedDictionary.cs +++ b/Runtime/Scripts/SerializedDictionary.cs @@ -73,6 +73,7 @@ private void SyncDictionaryToBackingField_Editor() get => base[key]; set { + base[key] = value; for (int i = 0; i < _serializedList.Count; i++) { var kvp = _serializedList[i]; @@ -81,8 +82,6 @@ private void SyncDictionaryToBackingField_Editor() kvp.Value = value; _serializedList[i] = kvp; } - - base[key] = value; } } From 02af012b3b94a01112d3d9d426c7b5e8d864aef9 Mon Sep 17 00:00:00 2001 From: ayellowpaper Date: Sat, 17 Jun 2023 20:42:03 +0200 Subject: [PATCH 08/14] draggable key label width --- Editor/Scripts/Data/PropertyData.cs | 8 +++ .../SerializedDictionaryInstanceDrawer.cs | 32 ++++++++-- Editor/Scripts/Utility/SCEditorUtility.cs | 63 +++++++++++++++++++ 3 files changed, 98 insertions(+), 5 deletions(-) diff --git a/Editor/Scripts/Data/PropertyData.cs b/Editor/Scripts/Data/PropertyData.cs index 208445e..515e41e 100644 --- a/Editor/Scripts/Data/PropertyData.cs +++ b/Editor/Scripts/Data/PropertyData.cs @@ -7,6 +7,8 @@ namespace AYellowpaper.SerializedCollections.Editor.Data [System.Serializable] internal class PropertyData { + [SerializeField] + private float _keyLabelWidth; [SerializeField] private ElementData _keyData; [SerializeField] @@ -20,6 +22,12 @@ public bool AlwaysShowSearch set => _alwaysShowSearch = value; } + public float KeyLabelWidth + { + get => _keyLabelWidth; + set => _keyLabelWidth = value; + } + public ElementData GetElementData(bool fieldType) { return fieldType == SCEditorUtility.KeyFlag ? _keyData : _valueData; diff --git a/Editor/Scripts/SerializedDictionaryInstanceDrawer.cs b/Editor/Scripts/SerializedDictionaryInstanceDrawer.cs index 2f80d8a..13a4ed1 100644 --- a/Editor/Scripts/SerializedDictionaryInstanceDrawer.cs +++ b/Editor/Scripts/SerializedDictionaryInstanceDrawer.cs @@ -15,6 +15,8 @@ namespace AYellowpaper.SerializedCollections.Editor { public class SerializedDictionaryInstanceDrawer { + private const float MinKeyValueLabelWidth = 40f; + private FieldInfo _fieldInfo; private ReorderableList _unexpandedList; private SingleEditingData _singleEditingData; @@ -419,14 +421,26 @@ private void ToggleAlwaysShowSearchPropertyData() private void DoKeyValueRect(Rect rect) { - float width = EditorGUIUtility.labelWidth + 22; + var width = GetDesiredKeyLabelWidth(rect.width, 22); Rect leftRect = rect.WithWidth(width); Rect rightRect = leftRect.AppendRight(rect.width - width); - if (Event.current.type == EventType.Repaint && _propertyData != null) + if (_propertyData != null) { - _keyValueStyle.Draw(leftRect, EditorGUIUtility.TrTextContent(_propertyData.GetElementData(SerializedDictionaryDrawer.KeyFlag).Settings.DisplayName), false, false, false, false); - _keyValueStyle.Draw(rightRect, EditorGUIUtility.TrTextContent(_propertyData.GetElementData(SerializedDictionaryDrawer.ValueFlag).Settings.DisplayName), false, false, false, false); + if (Event.current.type == EventType.Repaint) + { + _keyValueStyle.Draw(leftRect, EditorGUIUtility.TrTextContent(_propertyData.GetElementData(SerializedDictionaryDrawer.KeyFlag).Settings.DisplayName), false, false, false, false); + _keyValueStyle.Draw(rightRect, EditorGUIUtility.TrTextContent(_propertyData.GetElementData(SerializedDictionaryDrawer.ValueFlag).Settings.DisplayName), false, false, false, false); + } + var changeSizeRect = leftRect.AppendRight(5); + changeSizeRect.x -= 2; + EditorGUI.BeginChangeCheck(); + float newWidth = SCEditorUtility.DoHorizontalScale(changeSizeRect, _propertyData.KeyLabelWidth > 0f ? _propertyData.KeyLabelWidth : width); + if (EditorGUI.EndChangeCheck()) + { + _propertyData.KeyLabelWidth = Mathf.Max(newWidth, MinKeyValueLabelWidth); + SavePropertyData(); + } } if (ListProperty.minArraySize > 0) @@ -438,6 +452,14 @@ private void DoKeyValueRect(Rect rect) EditorGUI.DrawRect(rect.AppendDown(1, -1), SerializedDictionaryDrawer.BorderColor); } + private float GetDesiredKeyLabelWidth(float maxWidth, float offset = 0f) + { + float desiredWidth = _propertyData is { KeyLabelWidth: > 0 } + ? _propertyData.KeyLabelWidth + : EditorGUIUtility.labelWidth; + return Mathf.Clamp(desiredWidth + offset, MinKeyValueLabelWidth, maxWidth - MinKeyValueLabelWidth); + } + private void DoSearch(Rect rect) { EditorGUI.DrawRect(rect.AppendLeft(1), SerializedDictionaryDrawer.BorderColor); @@ -582,7 +604,7 @@ private void OnDrawElement(Rect rect, int index, bool isActive, bool isFocused) int actualIndex = _pagedIndices[index]; SerializedProperty kvp = _activeState.GetPropertyAtIndex(actualIndex); - Rect keyRect = rect.WithSize(EditorGUIUtility.labelWidth - lineLeftSpace, EditorGUIUtility.singleLineHeight); + Rect keyRect = rect.WithSize(GetDesiredKeyLabelWidth(rect.width) - lineLeftSpace, EditorGUIUtility.singleLineHeight); Rect lineRect = keyRect.WithXAndWidth(keyRect.x + keyRect.width + lineLeftSpace, lineWidth).WithHeight(rect.height); Rect valueRect = keyRect.AppendRight(rect.width - keyRect.width - totalSpace, totalSpace); diff --git a/Editor/Scripts/Utility/SCEditorUtility.cs b/Editor/Scripts/Utility/SCEditorUtility.cs index 395d824..60c113a 100644 --- a/Editor/Scripts/Utility/SCEditorUtility.cs +++ b/Editor/Scripts/Utility/SCEditorUtility.cs @@ -114,8 +114,71 @@ internal static bool TryGetTypeFromProperty(SerializedProperty property, out Typ return false; } } + + internal static float DoHorizontalScale(Rect rect, float value) + { + var controlId = GUIUtility.GetControlID(FocusType.Passive); + var isMovingMouse = Event.current.type == EventType.MouseDrag; + DoButtonControl(rect, controlId, false, false, GUIContent.none, GUIStyle.none); + + if (controlId == GUIUtility.hotControl && isMovingMouse) + { + value += Event.current.delta.x; + GUI.changed = true; + } + + EditorGUIUtility.AddCursorRect(rect, MouseCursor.ResizeHorizontal); + return value; + } + + internal static bool DoButtonControl(Rect rect, int id, bool on, bool hover, GUIContent content, GUIStyle style) + { + Event current = Event.current; + switch (current.type) + { + case EventType.MouseDown: + if (HitTest(rect, current.mousePosition)) + { + GUIUtility.hotControl = id; + current.Use(); + } + break; + case EventType.MouseUp: + if (GUIUtility.hotControl == id) + { + GUIUtility.hotControl = 0; + current.Use(); + if (HitTest(rect, current.mousePosition)) + { + GUI.changed = true; + return !on; + } + } + break; + case EventType.MouseDrag: + if (GUIUtility.hotControl == id) + { + current.Use(); + } + break; + case EventType.KeyDown: + bool flag = current.alt || current.shift || current.command || current.control; + if ((current.keyCode == KeyCode.Space || current.keyCode == KeyCode.Return || current.keyCode == KeyCode.KeypadEnter) && !flag && GUIUtility.keyboardControl == id) + { + current.Use(); + GUI.changed = true; + return !on; + } + break; + case EventType.Repaint: + style.Draw(rect, content, id, on, hover); + break; + } + return on; + } + internal static bool HitTest(Rect rect, Vector2 point) => point.x >= rect.xMin && point.x < rect.xMax && point.y >= rect.yMin && point.y < rect.yMax; public static object GetPropertyValue(SerializedProperty prop, object target) From 0581232b652fb7bfaf64e15b5475c47d30735d08 Mon Sep 17 00:00:00 2001 From: ayellowpaper Date: Sun, 25 Jun 2023 13:38:32 +0200 Subject: [PATCH 09/14] indexer now properly adds the new kvp. Also drawer doesn't break anymore when inheriting from SerializedDictionary --- Editor/Scripts/SerializedDictionaryDrawer.cs | 2 +- .../SerializedDictionaryInstanceDrawer.cs | 16 +++++++++++++++- Runtime/Scripts/SerializedDictionary.cs | 5 +++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Editor/Scripts/SerializedDictionaryDrawer.cs b/Editor/Scripts/SerializedDictionaryDrawer.cs index f66aa4a..67a1f76 100644 --- a/Editor/Scripts/SerializedDictionaryDrawer.cs +++ b/Editor/Scripts/SerializedDictionaryDrawer.cs @@ -16,7 +16,7 @@ namespace AYellowpaper.SerializedCollections.Editor #if ODIN_INSPECTOR [Sirenix.OdinInspector.Editor.DrawerPriority(Sirenix.OdinInspector.Editor.DrawerPriorityLevel.SuperPriority)] #endif - [CustomPropertyDrawer(typeof(SerializedDictionary<,>))] + [CustomPropertyDrawer(typeof(SerializedDictionary<,>), true)] public class SerializedDictionaryDrawer : PropertyDrawer { public const string KeyName = nameof(SerializedKeyValuePair.Key); diff --git a/Editor/Scripts/SerializedDictionaryInstanceDrawer.cs b/Editor/Scripts/SerializedDictionaryInstanceDrawer.cs index 13a4ed1..00a5907 100644 --- a/Editor/Scripts/SerializedDictionaryInstanceDrawer.cs +++ b/Editor/Scripts/SerializedDictionaryInstanceDrawer.cs @@ -180,7 +180,8 @@ private void InitializeSettingsIfNeeded() { void InitializeSettings(bool fieldFlag) { - var genericArgs = _fieldInfo.FieldType.GetGenericArguments(); + var dictionaryType = FindGenericBaseType(typeof(SerializedDictionary<,>), _fieldInfo.FieldType); + var genericArgs = dictionaryType.GetGenericArguments(); var firstProperty = ListProperty.GetArrayElementAtIndex(0); var keySettings = CreateDisplaySettings(GetElementProperty(firstProperty, fieldFlag), genericArgs[fieldFlag == SCEditorUtility.KeyFlag ? 0 : 1]); var settings = _propertyData.GetElementData(fieldFlag).Settings; @@ -197,6 +198,19 @@ void InitializeSettings(bool fieldFlag) } } + private static Type FindGenericBaseType(Type generic, Type toCheck) + { + while (toCheck != null && toCheck != typeof(object)) + { + var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck; + if (generic == cur) { + return cur; + } + toCheck = toCheck.BaseType; + } + return null; + } + private void CheckPaging() { // TODO: Is there a better solution to check for Revert/delete/add? diff --git a/Runtime/Scripts/SerializedDictionary.cs b/Runtime/Scripts/SerializedDictionary.cs index ba5328b..00a3f49 100644 --- a/Runtime/Scripts/SerializedDictionary.cs +++ b/Runtime/Scripts/SerializedDictionary.cs @@ -74,14 +74,19 @@ private void SyncDictionaryToBackingField_Editor() set { base[key] = value; + bool anyEntryWasFound = false; for (int i = 0; i < _serializedList.Count; i++) { var kvp = _serializedList[i]; if (!SerializedCollectionsUtility.KeysAreEqual(key, kvp.Key)) continue; + anyEntryWasFound = true; kvp.Value = value; _serializedList[i] = kvp; } + + if (!anyEntryWasFound) + _serializedList.Add(new SerializedKeyValuePair(key, value)); } } From d7558818c08c90ca895ce64f61a672228c3897b8 Mon Sep 17 00:00:00 2001 From: ayellowpaper Date: Thu, 27 Jul 2023 00:17:25 +0200 Subject: [PATCH 10/14] fixed custom drawer override --- Editor/Scripts/SerializedDictionaryInstanceDrawer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Editor/Scripts/SerializedDictionaryInstanceDrawer.cs b/Editor/Scripts/SerializedDictionaryInstanceDrawer.cs index 00a5907..ad34fd8 100644 --- a/Editor/Scripts/SerializedDictionaryInstanceDrawer.cs +++ b/Editor/Scripts/SerializedDictionaryInstanceDrawer.cs @@ -204,7 +204,7 @@ private static Type FindGenericBaseType(Type generic, Type toCheck) { var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck; if (generic == cur) { - return cur; + return toCheck; } toCheck = toCheck.BaseType; } From f3ab2da874cace0f24e984cde6d771dfb68c918f Mon Sep 17 00:00:00 2001 From: ayellowpaper Date: Thu, 27 Jul 2023 00:42:52 +0200 Subject: [PATCH 11/14] fix for deserialization --- Editor/Scripts/SerializedDictionaryInstanceDrawer.cs | 2 +- Runtime/Scripts/SerializedDictionary.cs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Editor/Scripts/SerializedDictionaryInstanceDrawer.cs b/Editor/Scripts/SerializedDictionaryInstanceDrawer.cs index ad34fd8..0c4a6cb 100644 --- a/Editor/Scripts/SerializedDictionaryInstanceDrawer.cs +++ b/Editor/Scripts/SerializedDictionaryInstanceDrawer.cs @@ -605,7 +605,7 @@ private float OnGetElementHeight(int index) { int actualIndex = _pagedIndices[index]; var element = _activeState.GetPropertyAtIndex(actualIndex); - return CalculateHeightOfElement(element, _propertyData.GetElementData(SerializedDictionaryDrawer.KeyFlag).EffectiveDisplayType == DisplayType.List ? true : false, _propertyData.GetElementData(SerializedDictionaryDrawer.ValueFlag).EffectiveDisplayType == DisplayType.List ? true : false); + return CalculateHeightOfElement(element, _propertyData.GetElementData(SerializedDictionaryDrawer.KeyFlag).EffectiveDisplayType == DisplayType.List, _propertyData.GetElementData(SerializedDictionaryDrawer.ValueFlag).EffectiveDisplayType == DisplayType.List); } private void OnDrawElement(Rect rect, int index, bool isActive, bool isFocused) diff --git a/Runtime/Scripts/SerializedDictionary.cs b/Runtime/Scripts/SerializedDictionary.cs index 00a3f49..be4296c 100644 --- a/Runtime/Scripts/SerializedDictionary.cs +++ b/Runtime/Scripts/SerializedDictionary.cs @@ -162,6 +162,10 @@ public void OnBeforeSerialize() #if UNITY_EDITOR if (UnityEditor.BuildPipeline.isBuildingPlayer) LookupTable.RemoveDuplicates(); + + // TODO: is there a better way to check if the dictionary was deserialized with reflection? + if (_serializedList.Count == 0 && Count > 0) + SyncDictionaryToBackingField_Editor(); #else foreach (var kvp in this) _serializedList.Add(new SerializedKeyValuePair(kvp.Key, kvp.Value)); From 0e48353cb1633b4e315555f23e7fc188a3488e30 Mon Sep 17 00:00:00 2001 From: ayellowpaper Date: Wed, 2 Aug 2023 02:15:20 +0200 Subject: [PATCH 12/14] fixed an error with inherited member --- Editor/Scripts/Utility/SCEditorUtility.cs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Editor/Scripts/Utility/SCEditorUtility.cs b/Editor/Scripts/Utility/SCEditorUtility.cs index 60c113a..d8c5992 100644 --- a/Editor/Scripts/Utility/SCEditorUtility.cs +++ b/Editor/Scripts/Utility/SCEditorUtility.cs @@ -206,10 +206,10 @@ public static object GetValue(object source, string name) if (source == null) return null; var type = source.GetType(); - var f = type.GetField(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); + var f = type.GetFieldRecursive(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); if (f == null) { - var p = type.GetProperty(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); + var p = type.GetPropertyRecursive(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); if (p == null) return null; return p.GetValue(source, null); @@ -225,5 +225,21 @@ public static object GetValue(object source, string name, int index) enm.MoveNext(); return enm.Current; } + + private static FieldInfo GetFieldRecursive(this Type type, string name, BindingFlags bindingFlags) + { + var fieldInfo = type.GetField(name, bindingFlags); + if (fieldInfo == null && type.BaseType != null) + return type.BaseType.GetFieldRecursive(name, bindingFlags); + return fieldInfo; + } + + private static PropertyInfo GetPropertyRecursive(this Type type, string name, BindingFlags bindingFlags) + { + var propertyInfo = type.GetProperty(name, bindingFlags); + if (propertyInfo == null && type.BaseType != null) + return type.BaseType.GetPropertyRecursive(name, bindingFlags); + return propertyInfo; + } } } \ No newline at end of file From 4fedb68b19d704defef4cc48133a25d083fb5bec Mon Sep 17 00:00:00 2001 From: ayellowpaper Date: Thu, 7 Dec 2023 00:30:47 +0100 Subject: [PATCH 13/14] fixed error in 2023 --- Editor/Scripts/SerializedDictionaryInstanceDrawer.cs | 2 +- Editor/Scripts/Utility/SCEditorUtility.cs | 6 +++--- Runtime/Scripts/SerializedDictionary.cs | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Editor/Scripts/SerializedDictionaryInstanceDrawer.cs b/Editor/Scripts/SerializedDictionaryInstanceDrawer.cs index 0c4a6cb..ce8f52b 100644 --- a/Editor/Scripts/SerializedDictionaryInstanceDrawer.cs +++ b/Editor/Scripts/SerializedDictionaryInstanceDrawer.cs @@ -304,7 +304,7 @@ private void OnDrawNoneElement(Rect rect) private (DisplayType displayType, bool canToggleListDrawer) CreateDisplaySettings(SerializedProperty property, Type type) { - bool hasCustomEditor = SCEditorUtility.HasDrawerForType(type); + bool hasCustomEditor = SCEditorUtility.HasDrawerForProperty(property, type); bool isGenericWithChildren = property.propertyType == SerializedPropertyType.Generic && property.hasVisibleChildren; bool isArray = property.isArray && property.propertyType != SerializedPropertyType.String; bool canToggleListDrawer = isArray || (isGenericWithChildren && hasCustomEditor); diff --git a/Editor/Scripts/Utility/SCEditorUtility.cs b/Editor/Scripts/Utility/SCEditorUtility.cs index d8c5992..131d998 100644 --- a/Editor/Scripts/Utility/SCEditorUtility.cs +++ b/Editor/Scripts/Utility/SCEditorUtility.cs @@ -70,15 +70,15 @@ public static bool ShouldShowSearch(int pages) return settings.AlwaysShowSearch ? true : pages >= settings.PageCountForSearch; } - public static bool HasDrawerForType(Type type) + public static bool HasDrawerForProperty(SerializedProperty property, Type type) { Type attributeUtilityType = typeof(SerializedProperty).Assembly.GetType("UnityEditor.ScriptAttributeUtility"); if (attributeUtilityType == null) return false; - var getDrawerMethod = attributeUtilityType.GetMethod("GetDrawerTypeForType", BindingFlags.Static | BindingFlags.NonPublic); + var getDrawerMethod = attributeUtilityType.GetMethod("GetDrawerTypeForPropertyAndType", BindingFlags.Static | BindingFlags.NonPublic); if (getDrawerMethod == null) return false; - return getDrawerMethod.Invoke(null, new object[] { type }) != null; + return getDrawerMethod.Invoke(null, new object[] { property, type }) != null; } internal static void AddGenericMenuItem(GenericMenu genericMenu, bool isOn, bool isEnabled, GUIContent content, GenericMenu.MenuFunction action) diff --git a/Runtime/Scripts/SerializedDictionary.cs b/Runtime/Scripts/SerializedDictionary.cs index be4296c..f932f3d 100644 --- a/Runtime/Scripts/SerializedDictionary.cs +++ b/Runtime/Scripts/SerializedDictionary.cs @@ -167,6 +167,7 @@ public void OnBeforeSerialize() if (_serializedList.Count == 0 && Count > 0) SyncDictionaryToBackingField_Editor(); #else + _serializedList.Clear(); foreach (var kvp in this) _serializedList.Add(new SerializedKeyValuePair(kvp.Key, kvp.Value)); #endif From 26a5c27bb07e850536e9296ead940116adbe9d4d Mon Sep 17 00:00:00 2001 From: Noah Ratcliff Date: Wed, 22 May 2024 07:12:45 -0400 Subject: [PATCH 14/14] Update package version to 1.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5e5c301..76eebc4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ayellowpaper.serialized-dictionary", - "version": "1.0.0", + "version": "1.1.0", "displayName": "Serialized Dictionary", "unity": "2021.3", "description": "Serialized Dictionary enables you to serialize dictionaries, including a very easy to use, native-feeling editor, search, duplicate keys in editor for testing and debugging and much more.",