added serialzed dictionary

This commit is contained in:
Sebastian Bularca
2026-03-19 22:20:34 +01:00
parent fedd1961a0
commit 9ec4c75ce2
129 changed files with 4088 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
.claude/
.idea/

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 43bd94852ddc6b34dbe9fec318fb54d8
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,17 @@
{
"name": "AYellowpaper.SerializedCollections.Editor",
"references": [
"GUID:d525ad6bd40672747bde77962f1c401e"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 234340c04eed5674a988bc6ebde7d248
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: edb739dfb1824234681f6480c75cd523
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,170 @@
fileFormatVersion: 2
guid: 8deddfed9f39d7740879d2cb0fcf7ce0
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 11
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMasterTextureLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: iPhone
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Windows Store Apps
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,110 @@
.sc-close-button {
background-image: resource('d_winbtn_win_close@2x');
width: 16px;
height: 16px;
position: absolute;
right: 2px;
top: 2px;
background-color: rgba(0, 0, 0, 0);
border-left-width: 0;
border-right-width: 0;
border-top-width: 0;
border-bottom-width: 0;
}
.sc-close-button:hover {
background-color: rgba(80, 80, 80, 255);
}
.sc-close-button:active {
background-color: rgba(0, 0, 0, 0);
}
.sc-text-toggle {
margin: 0;
padding: 0;
justify-content: center;
}
.sc-text-toggle:checked {
-unity-font-style: bold;
}
.sc-text-toggle > Label {
min-width: auto;
width: 100%;
height: 100%;
}
.sc-text-toggle > .unity-radio-button__input {
display: none;
}
.sc-title {
background-color: rgba(40, 40, 40, 0.35);
padding-left: 4px;
padding-right: 3px;
padding-top: 3px;
padding-bottom: 3px;
border-left-color: rgb(25, 25, 25);
border-right-color: rgb(25, 25, 25);
border-top-color: rgb(25, 25, 25);
border-bottom-color: rgb(25, 25, 25);
border-bottom-width: 1px;
-unity-font-style: bold;
}
.sc-generator-toggle {
padding-left: 3px;
padding-right: 3px;
padding-top: 3px;
padding-bottom: 3px;
}
.sc-generator-toggle:hover {
background-color: rgb(48, 48, 48);
}
.sc-generator-toggle:checked {
background-color: rgb(77, 77, 77);
}
.sc-modification-toggle {
flex-basis: 100%;
flex-shrink: 1;
border-left-color: rgb(41, 41, 41);
border-right-color: rgb(41, 41, 41);
border-top-color: rgb(41, 41, 41);
border-bottom-color: rgb(41, 41, 41);
border-left-width: 1px;
border-right-width: 1px;
border-top-width: 1px;
border-bottom-width: 1px;
-unity-font-style: normal;
-unity-text-align: middle-center;
padding-left: 2px;
padding-right: 2px;
padding-top: 2px;
padding-bottom: 2px;
}
.sc-modification-toggle:hover {
background-color: rgb(70, 70, 70);
}
.sc-modification-toggle:checked {
background-color: rgb(80, 80, 80);
}
.sc-radio-button-group {
padding: 0;
margin: 0;
}
.sc-radio-button-group > Label {
display: none;
}
#generators-group {
flex-direction: column;
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: df6c2ef835e40c94c976442569324029
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
disableValidation: 0

View File

@@ -0,0 +1,26 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
<Style src="project://database/Assets/Plugins/SerializedCollections/Editor/Assets/KeysGeneratorSelectorWindow.uss?fileID=7433441132597879392&amp;guid=df6c2ef835e40c94c976442569324029&amp;type=3#KeysGeneratorSelectorWindow" />
<ui:VisualElement style="flex-direction: row; flex-grow: 1; border-left-color: rgb(97, 97, 97); border-right-color: rgb(97, 97, 97); border-top-color: rgb(97, 97, 97); border-bottom-color: rgb(97, 97, 97); border-left-width: 2px; border-right-width: 2px; border-top-width: 2px; border-bottom-width: 2px;">
<ui:VisualElement name="LeftContent" style="flex-basis: 66%; border-left-color: rgb(25, 25, 25); border-right-color: rgb(25, 25, 25); border-top-color: rgb(25, 25, 25); border-bottom-color: rgb(25, 25, 25); border-right-width: 1px;">
<ui:Label text="Generators" display-tooltip-when-elided="true" class="sc-title" />
<ui:ScrollView scroll-deceleration-rate="0,135" elasticity="0,1" name="generators-content" style="flex-grow: 1;" />
</ui:VisualElement>
<ui:VisualElement name="RightContent" style="flex-basis: 100%;">
<ui:Label text="Inspector" display-tooltip-when-elided="true" class="sc-title" />
<ui:IMGUIContainer name="imgui-inspector" style="flex-grow: 1; margin-left: 4px; margin-right: 4px; margin-top: 4px; margin-bottom: 4px;" />
<ui:Label text="4 Elements " display-tooltip-when-elided="true" name="generated-count-label" style="padding-left: 2px; padding-right: 2px; padding-top: 2px; padding-bottom: 2px;" />
<ui:VisualElement style="flex-direction: row;">
<ui:RadioButtonGroup label="Radio Button Group" value="-1" name="modification-group" class="sc-radio-button-group" style="flex-grow: 1;">
<ui:RadioButton label="Add" name="add-modification" tooltip="Add the generated missing keys to the target." class="sc-text-toggle sc-modification-toggle" />
<ui:RadioButton label="Remove" name="remove-modification" tooltip="Remove the generated keys form the target." class="sc-text-toggle sc-modification-toggle" />
<ui:RadioButton label="Confine" name="confine-modification" tooltip="Remove all keys that are not part of the generated keys from the target." class="sc-text-toggle sc-modification-toggle" />
</ui:RadioButtonGroup>
</ui:VisualElement>
<ui:VisualElement style="flex-direction: row;">
<ui:Label display-tooltip-when-elided="true" name="result-label" style="flex-grow: 1; -unity-text-align: middle-left; padding-left: 2px; padding-right: 2px; padding-top: 2px; padding-bottom: 2px;" />
<ui:Button text="Apply" display-tooltip-when-elided="true" name="apply-button" style="width: 100px;" />
</ui:VisualElement>
</ui:VisualElement>
<ui:Button display-tooltip-when-elided="true" class="sc-close-button" />
</ui:VisualElement>
</ui:UXML>

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 681c8a924c8b1e14b9fe53bb7397ec3d
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6398825db0c1d5348b5ec85b0c12d9c0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 81bf46994f9232b4e8dcf467c109f046
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace AYellowpaper.SerializedCollections.Editor.Data
{
[System.Serializable]
internal class ElementData
{
[SerializeField]
private bool _isListToggleActive = false;
public ElementSettings Settings { get; }
public bool ShowAsList => Settings.HasListDrawerToggle && IsListToggleActive;
public bool IsListToggleActive { get => _isListToggleActive; set => _isListToggleActive = value; }
public DisplayType EffectiveDisplayType => ShowAsList ? DisplayType.List : Settings.DisplayType;
public ElementData(ElementSettings elementSettings)
{
Settings = elementSettings;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 650a80186cc93b54aa5627197c23ea6e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace AYellowpaper.SerializedCollections.Editor.Data
{
public class ElementSettings
{
public const string DefaultName = "Not Set";
public string DisplayName { get; set; } = DefaultName;
public DisplayType DisplayType { get; set; } = DisplayType.PropertyNoLabel;
public bool HasListDrawerToggle { get; set; } = false;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 12edfc8006691b7498459540b832c5eb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,36 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace AYellowpaper.SerializedCollections.Editor.Data
{
[System.Serializable]
internal class PropertyData
{
[SerializeField]
private ElementData _keyData;
[SerializeField]
private ElementData _valueData;
[SerializeField]
private bool _alwaysShowSearch = false;
public bool AlwaysShowSearch
{
get => _alwaysShowSearch;
set => _alwaysShowSearch = value;
}
public ElementData GetElementData(bool fieldType)
{
return fieldType == SCEditorUtility.KeyFlag ? _keyData : _valueData;
}
public PropertyData() : this(new ElementSettings(), new ElementSettings()) { }
public PropertyData(ElementSettings keySettings, ElementSettings valueSettings)
{
_keyData = new ElementData(keySettings);
_valueData = new ElementData(valueSettings);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ba2d792dfc6653e46bee7c027202acd3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,13 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace AYellowpaper.SerializedCollections.Editor
{
public enum DisplayType
{
Property,
PropertyNoLabel,
List
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 37ee13dd67b545846b6b063923812943
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d74b279cd03ebfb4ca7d2606f3f4f11e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 10b0c99dadaea0e42b49e323700f2eb9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace AYellowpaper.SerializedCollections.KeysGenerators
{
[KeyListGenerator("Populate Enum", typeof(System.Enum), false)]
public class EnumGenerator : KeyListGenerator
{
public override IEnumerable GetKeys(System.Type type)
{
return System.Enum.GetValues(type);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d639de5d36bbeea4496c97cc3f1f4e81
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections;
using UnityEngine;
namespace AYellowpaper.SerializedCollections.KeysGenerators
{
[KeyListGenerator("Int Range", typeof(int))]
public class IntRangeGenerator : KeyListGenerator
{
[SerializeField]
private int _startValue = 1;
[SerializeField]
private int _endValue = 10;
public override IEnumerable GetKeys(Type type)
{
int dir = Math.Sign(_endValue - _startValue);
dir = dir == 0 ? 1 : dir;
for (int i = _startValue; i != _endValue; i += dir)
yield return i;
yield return _endValue;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a4203f3a582fa874fb035633bd9892b4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,26 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace AYellowpaper.SerializedCollections.KeysGenerators
{
[KeyListGenerator("Int Stepping", typeof(int))]
public class IntSteppingGenerator : KeyListGenerator
{
[SerializeField]
private int _startIndex = 0;
[SerializeField]
private int _stepDistance = 10;
[SerializeField, Min(0)]
private int _stepCount = 1;
public override IEnumerable GetKeys(Type type)
{
for (int i = 0; i <= _stepCount; i++)
{
yield return _startIndex + i * _stepDistance;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 99302d4ff0ae27b4898ced57890ed32d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,11 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace AYellowpaper.SerializedCollections.KeysGenerators
{
public abstract class KeyListGenerator : ScriptableObject
{
public abstract IEnumerable GetKeys(System.Type type);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 08149f25a1b9c5e48a224a7c4d31a154
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,19 @@
using System;
namespace AYellowpaper.SerializedCollections.KeysGenerators
{
[AttributeUsage(AttributeTargets.Class)]
public class KeyListGeneratorAttribute : Attribute
{
public readonly string Name;
public readonly Type TargetType;
public readonly bool NeedsWindow;
public KeyListGeneratorAttribute(string name, Type targetType, bool needsWindow = true)
{
Name = name;
TargetType = targetType;
NeedsWindow = needsWindow;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0f6065c936425ab478ecc8a8cf30d38f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,36 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace AYellowpaper.SerializedCollections.KeysGenerators
{
public static class KeyListGeneratorCache
{
private static List<KeyListGeneratorData> _populators;
private static Dictionary<Type, List<KeyListGeneratorData>> _populatorsByType;
static KeyListGeneratorCache()
{
_populators = new List<KeyListGeneratorData>();
_populatorsByType = new Dictionary<Type, List<KeyListGeneratorData>>();
var populatorTypes = TypeCache.GetTypesDerivedFrom<KeyListGenerator>();
foreach (var populatorType in populatorTypes.Where(x => !x.IsAbstract))
{
var attributes = populatorType.GetCustomAttributes<KeyListGeneratorAttribute>();
foreach (var attribute in attributes)
_populators.Add(new KeyListGeneratorData(attribute.Name, attribute.TargetType, populatorType, attribute.NeedsWindow));
}
}
public static IReadOnlyList<KeyListGeneratorData> GetPopulatorsForType(Type type)
{
if (!_populatorsByType.ContainsKey(type))
_populatorsByType.Add(type, new List<KeyListGeneratorData>(_populators.Where(x => x.TargetType.IsAssignableFrom(type))));
return _populatorsByType[type];
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7936217ae19613d4bb5e46e73a4a587c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,20 @@
using System;
namespace AYellowpaper.SerializedCollections.KeysGenerators
{
public class KeyListGeneratorData
{
public string Name { get; set; }
public Type TargetType { get; set; }
public Type GeneratorType { get; set; }
public bool NeedsWindow { get; set; }
public KeyListGeneratorData(string name, Type targetType, Type populatorType, bool needsWindow)
{
Name = name;
TargetType = targetType;
GeneratorType = populatorType;
NeedsWindow = needsWindow;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8992145ced672cd46a425fb2590b80bb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,26 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
namespace AYellowpaper.SerializedCollections.KeysGenerators
{
[CustomEditor(typeof(KeyListGenerator), true)]
public class KeyListGeneratorEditor : UnityEditor.Editor
{
public override void OnInspectorGUI()
{
var iterator = serializedObject.GetIterator();
if (iterator.Next(true))
{
// skip script name
iterator.NextVisible(true);
while (iterator.NextVisible(true))
{
EditorGUILayout.PropertyField(iterator);
}
}
serializedObject.ApplyModifiedProperties();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 211518bea98ede643af247b6295984bd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,182 @@
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace AYellowpaper.SerializedCollections.KeysGenerators
{
public class KeyListGeneratorSelectorWindow : EditorWindow
{
[SerializeField]
private int _selectedIndex;
[SerializeField]
private ModificationType _modificationType;
private KeyListGenerator _generator;
private UnityEditor.Editor _editor;
private List<KeyListGeneratorData> _generatorsData;
private Type _targetType;
private int _undoStart;
private Dictionary<Type, KeyListGenerator> _keysGenerators = new Dictionary<Type, KeyListGenerator>();
private string _detailsText;
public event Action<KeyListGenerator, ModificationType> OnApply;
private void OnEnable()
{
VisualTreeAsset document = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Plugins/SerializedCollections/Editor/Assets/KeysGeneratorSelectorWindow.uxml");
var element = document.CloneTree();
element.style.height = new StyleLength(new Length(100, LengthUnit.Percent));
rootVisualElement.Add(element);
}
public void Initialize(IEnumerable<KeyListGeneratorData> generatorsData, Type type)
{
_targetType = type;
_selectedIndex = 0;
_modificationType = ModificationType.Add;
_undoStart = Undo.GetCurrentGroup();
_generatorsData = new List<KeyListGeneratorData>(generatorsData);
SetGeneratorIndex(0);
Undo.undoRedoPerformed += HandleUndoCallback;
rootVisualElement.Q<Button>(className: "sc-close-button").clicked += Close;
rootVisualElement.Q<RadioButton>(name = "add-modification").userData = ModificationType.Add;
rootVisualElement.Q<RadioButton>(name = "remove-modification").userData = ModificationType.Remove;
rootVisualElement.Q<RadioButton>(name = "confine-modification").userData = ModificationType.Confine;
var modificationToggles = rootVisualElement.Query<RadioButton>(className: "sc-modification-toggle");
modificationToggles.ForEach(InitializeModificationToggle);
rootVisualElement.Q<IMGUIContainer>(name = "imgui-inspector").onGUIHandler = EditorGUIHandler;
rootVisualElement.Q<Button>(name = "apply-button").clicked += ApplyButtonClicked;
var generatorsContent = rootVisualElement.Q<ScrollView>(name = "generators-content");
var radioButtonGroup = new RadioButtonGroup();
radioButtonGroup.name = "generators-group";
radioButtonGroup.AddToClassList("sc-radio-button-group");
generatorsContent.Add(radioButtonGroup);
for (int i = 0; i < _generatorsData.Count; i++)
{
var generatorData = _generatorsData[i];
var radioButton = new RadioButton(generatorData.Name);
radioButton.value = i == 0;
radioButton.AddToClassList("sc-text-toggle");
radioButton.AddToClassList("sc-generator-toggle");
radioButton.userData = i;
radioButton.RegisterValueChangedCallback(OnGeneratorClicked);
radioButtonGroup.Add(radioButton);
}
}
private void ApplyButtonClicked()
{
OnApply?.Invoke(_editor.target as KeyListGenerator, _modificationType);
OnApply = null;
Close();
}
private void EditorGUIHandler()
{
EditorGUI.BeginChangeCheck();
_editor.OnInspectorGUI();
if (EditorGUI.EndChangeCheck())
{
UpdateDetailsText();
}
}
private void InitializeModificationToggle(RadioButton obj)
{
if ((ModificationType)obj.userData == _modificationType)
obj.value = true;
obj.RegisterValueChangedCallback(OnModificationToggleClicked);
}
private void OnModificationToggleClicked(ChangeEvent<bool> evt)
{
if (!evt.newValue)
return;
var modificationType = (ModificationType)((VisualElement)evt.target).userData;
_modificationType = modificationType;
}
private void UpdateDetailsText()
{
var enumerable = _generator.GetKeys(_targetType);
int count = 0;
var enumerator = enumerable.GetEnumerator();
while (enumerator.MoveNext())
{
count++;
if (count > 100)
{
_detailsText = "over 100 Elements";
return;
}
}
_detailsText = $"{count} Elements";
rootVisualElement.Q<Label>(name = "generated-count-label").text = _detailsText;
}
private void OnDestroy()
{
Undo.undoRedoPerformed -= HandleUndoCallback;
Undo.RevertAllDownToGroup(_undoStart);
foreach (var keyGenerator in _keysGenerators)
DestroyImmediate(keyGenerator.Value);
}
private void OnGeneratorClicked(ChangeEvent<bool> evt)
{
if (!evt.newValue)
return;
SetGeneratorIndex((int)(evt.target as VisualElement).userData);
}
private void HandleUndoCallback()
{
UpdateGeneratorAndEditorIfNeeded();
Repaint();
}
private void SetGeneratorIndex(int index)
{
Undo.RecordObject(this, "Change Window");
_selectedIndex = index;
UpdateGeneratorAndEditorIfNeeded();
}
private void UpdateGeneratorAndEditorIfNeeded()
{
var targetType = _generatorsData[_selectedIndex].GeneratorType;
if (_generator != null && _generator.GetType() == targetType)
return;
_generator = GetOrCreateKeysGenerator(targetType);
if (_editor != null)
DestroyImmediate(_editor);
_editor = UnityEditor.Editor.CreateEditor(_generator);
UpdateDetailsText();
}
private KeyListGenerator GetOrCreateKeysGenerator(Type type)
{
if (!_keysGenerators.ContainsKey(type))
{
var so = (KeyListGenerator)CreateInstance(type);
so.hideFlags = HideFlags.DontSave;
_keysGenerators.Add(type, so);
}
return _keysGenerators[type];
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: df07fe6d161e3334083f837a066dd949
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,10 @@
namespace AYellowpaper.SerializedCollections
{
public enum ModificationType
{
None,
Add,
Remove,
Confine,
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fc2030edc0a04b74a8642773bd8c98fd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,68 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
namespace AYellowpaper.SerializedCollections.Editor
{
public class PagingElement
{
public int Page
{
get => _page;
set
{
_page = value;
EnsureValidPageIndex();
}
}
public int PageCount
{
get => _pageCount;
set
{
Debug.Assert(value >= 1, $"{nameof(PageCount)} needs to be 1 or larger but is {value}.");
_pageCount = value;
EnsureValidPageIndex();
}
}
private const int buttonWidth = 20;
private const int inputWidth = 20;
private const int labelWidth = 30;
private int _page = 1;
private int _pageCount = 1;
public PagingElement(int pageCount = 1)
{
PageCount = pageCount;
}
public float GetDesiredWidth()
{
return buttonWidth * 2 + inputWidth + labelWidth;
}
public void OnGUI(Rect rect)
{
Rect leftButton = rect.WithXAndWidth(rect.x, buttonWidth);
Rect inputRect = leftButton.AppendRight(inputWidth);
Rect labelRect = inputRect.AppendRight(labelWidth);
Rect rightButton = labelRect.AppendRight(buttonWidth);
using (new GUIEnabledScope(Page != 1))
if (GUI.Button(leftButton, "<"))
Page--;
using (new GUIEnabledScope(Page != PageCount))
if (GUI.Button(rightButton, ">"))
Page++;
Page = EditorGUI.IntField(inputRect, Page);
GUI.Label(labelRect, "/" + PageCount.ToString());
}
private void EnsureValidPageIndex()
{
_page = Mathf.Clamp(_page, 1, PageCount);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f35ac465aece4064582910fc2c206682
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: bead4ae311155bd46af1e99788f1d932
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 77b8ccff4f0c60943b9a54ce8d698d7f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,21 @@
using System;
using UnityEditor;
namespace AYellowpaper.SerializedCollections.Editor.Search
{
public class EnumMatcher : Matcher
{
public override bool IsMatch(SerializedProperty property)
{
if (property.propertyType == SerializedPropertyType.Enum && SCEditorUtility.TryGetTypeFromProperty(property, out var type))
{
foreach (var text in SCEnumUtility.GetEnumCache(type).GetNamesForValue(property.enumValueFlag))
{
if (text.Contains(SearchString, StringComparison.OrdinalIgnoreCase))
return true;
}
}
return false;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f2cf86e71fb0ff04f96ac71dd9961962
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,20 @@
using UnityEditor;
namespace AYellowpaper.SerializedCollections.Editor.Search
{
public abstract class Matcher
{
public string SearchString { get; private set; }
public void Prepare(string searchString)
{
SearchString = ProcessSearchString(searchString);
}
public virtual string ProcessSearchString(string searchString)
{
return searchString;
}
public abstract bool IsMatch(SerializedProperty property);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 218ea85e2d8480a498e8647aad524c8d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,30 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace AYellowpaper.SerializedCollections.Editor.Search
{
public static class Matchers
{
public static IEnumerable<Matcher> RegisteredMatchers => _registeredMatchers;
private static List<Matcher> _registeredMatchers = new List<Matcher>();
static Matchers()
{
_registeredMatchers.Add(new NumericMatcher());
_registeredMatchers.Add(new StringMatcher());
_registeredMatchers.Add(new EnumMatcher());
}
public static void AddMatcher(Matcher matcher)
{
_registeredMatchers.Add(matcher);
}
public static bool RemoveMatcher(Matcher matcher)
{
return _registeredMatchers.Remove(matcher);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 94535827bd107e549baa23442df58bef
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,109 @@
using System.Globalization;
using UnityEditor;
using UnityEngine;
namespace AYellowpaper.SerializedCollections.Editor.Search
{
public class NumericMatcher : Matcher
{
public override string ProcessSearchString(string searchString)
{
return searchString.Replace(',', '.');
}
public override bool IsMatch(SerializedProperty property)
{
if (property.propertyType == SerializedPropertyType.Float)
{
return IsFloatMatch(property.floatValue);
}
else if (property.propertyType == SerializedPropertyType.Integer)
{
return IsIntMatch(property.intValue);
}
else if (property.propertyType == SerializedPropertyType.Quaternion)
{
var quat = property.quaternionValue;
return IsFloatMatch(quat.x) || IsFloatMatch(quat.y) || IsFloatMatch(quat.z) || IsFloatMatch(quat.w);
}
else if (property.propertyType == SerializedPropertyType.Bounds)
{
var bounds = property.boundsValue;
return IsVector3Match(bounds.center) || IsVector3Match(bounds.size);
}
else if (property.propertyType == SerializedPropertyType.BoundsInt)
{
var bounds = property.boundsIntValue;
return IsVector3Match(bounds.center) || IsVector3IntMatch(bounds.size);
}
else if (property.propertyType == SerializedPropertyType.Rect)
{
var rect = property.rectValue;
return IsVector2Match(rect.size) || IsVector2Match(rect.position);
}
else if (property.propertyType == SerializedPropertyType.RectInt)
{
var rect = property.rectIntValue;
return IsVector2IntMatch(rect.size) || IsVector2IntMatch(rect.position);
}
else if (property.propertyType == SerializedPropertyType.Vector2)
{
return IsVector2Match(property.vector2Value);
}
else if (property.propertyType == SerializedPropertyType.Vector2Int)
{
return IsVector2IntMatch(property.vector2IntValue);
}
else if (property.propertyType == SerializedPropertyType.Vector3)
{
return IsVector3Match(property.vector3Value);
}
else if (property.propertyType == SerializedPropertyType.Vector3Int)
{
return IsVector3IntMatch(property.vector3IntValue);
}
else if (property.propertyType == SerializedPropertyType.Vector4)
{
return IsVector4Match(property.vector4Value);
}
return false;
}
private bool IsFloatMatch(float val)
{
var str = val.ToString(CultureInfo.InvariantCulture);
return str.Contains(SearchString, System.StringComparison.OrdinalIgnoreCase);
}
private bool IsIntMatch(int val)
{
var str = val.ToString(CultureInfo.InvariantCulture);
return str.Contains(SearchString, System.StringComparison.OrdinalIgnoreCase);
}
private bool IsVector2Match(Vector2 vector)
{
return IsFloatMatch(vector.x) || IsFloatMatch(vector.y);
}
private bool IsVector2IntMatch(Vector2Int vector)
{
return IsIntMatch(vector.x) || IsIntMatch(vector.y);
}
private bool IsVector3Match(Vector3 vector)
{
return IsFloatMatch(vector.x) || IsFloatMatch(vector.y) || IsFloatMatch(vector.z);
}
private bool IsVector3IntMatch(Vector3Int vector)
{
return IsIntMatch(vector.x) || IsIntMatch(vector.y) || IsIntMatch(vector.z);
}
private bool IsVector4Match(Vector4 vector)
{
return IsFloatMatch(vector.x) || IsFloatMatch(vector.y) || IsFloatMatch(vector.z) || IsFloatMatch(vector.w);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cc3ee6ec420212140bd3a86688d548de
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
using System.Globalization;
using UnityEditor;
namespace AYellowpaper.SerializedCollections.Editor.Search
{
public class StringMatcher : Matcher
{
public override bool IsMatch(SerializedProperty property)
{
if ((property.propertyType is SerializedPropertyType.String or SerializedPropertyType.Character) && property.stringValue.Contains(SearchString, System.StringComparison.OrdinalIgnoreCase))
return true;
return false;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: be8bbc3471f8ad04b8da6366285443c6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,22 @@
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace AYellowpaper.SerializedCollections.Editor.Search
{
public class PropertySearchResult
{
public SerializedProperty Property;
public PropertySearchResult(SerializedProperty property)
{
Property = property;
}
public override string ToString()
{
return $"Found match in in {Property.propertyPath}";
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 33b4e4c67054c4d488fa66ff14bd3120
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,66 @@
using System.Collections.Generic;
using UnityEditor;
namespace AYellowpaper.SerializedCollections.Editor.Search
{
public class SearchQuery
{
public string SearchString
{
get => _text;
set
{
if (_text == value)
return;
_text = value;
foreach (var matcher in _matchers)
matcher.Prepare(_text);
}
}
private IEnumerable<Matcher> _matchers;
private string _text;
public SearchQuery(IEnumerable<Matcher> matchers)
{
_matchers = matchers;
}
public List<PropertySearchResult> ApplyToProperty(SerializedProperty property)
{
TryGetMatchingProperties(property.Copy(), out var properties);
return properties;
}
public IEnumerable<SearchResultEntry> ApplyToArrayProperty(SerializedProperty property)
{
int arrayCount = property.arraySize;
for (int i = 0; i < arrayCount; i++)
{
var prop = property.GetArrayElementAtIndex(i);
if (TryGetMatchingProperties(prop.Copy(), out var properties))
yield return new SearchResultEntry(i, prop, properties);
}
}
private bool TryGetMatchingProperties(SerializedProperty property, out List<PropertySearchResult> matchingProperties)
{
matchingProperties = null;
foreach (var child in SCEditorUtility.GetChildren(property, true))
{
foreach (var matcher in _matchers)
{
if (matcher.IsMatch(child))
{
if (matchingProperties == null)
matchingProperties = new();
matchingProperties.Add(new PropertySearchResult(child.Copy()));
}
}
}
return matchingProperties != null;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c87dd0caf3c44ed4e82896aa40bf1b96
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,31 @@
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEditor;
using UnityEngine;
namespace AYellowpaper.SerializedCollections.Editor.Search
{
public class SearchResultEntry
{
public readonly int Index;
public readonly SerializedProperty Property;
public readonly IEnumerable<PropertySearchResult> MatchingResults;
public SearchResultEntry(int index, SerializedProperty property, IEnumerable<PropertySearchResult> matchingResults)
{
Index = index;
Property = property;
MatchingResults = matchingResults;
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine($"{Index}: {Property.propertyPath}");
foreach (var matchingResult in MatchingResults)
sb.AppendLine(matchingResult.ToString());
return sb.ToString();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d8455216ad2701d49b6dfec7f98ca88b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,62 @@
using AYellowpaper.SerializedCollections.Editor.Data;
using AYellowpaper.SerializedCollections.Editor.States;
using AYellowpaper.SerializedCollections.KeysGenerators;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEditorInternal;
using UnityEngine;
namespace AYellowpaper.SerializedCollections.Editor
{
[CustomPropertyDrawer(typeof(SerializedDictionary<,>))]
public class SerializedDictionaryDrawer : PropertyDrawer
{
public const string KeyName = nameof(SerializedKeyValuePair<int, int>.Key);
public const string ValueName = nameof(SerializedKeyValuePair<int, int>.Value);
public const string SerializedListName = nameof(SerializedDictionary<int, int>._serializedList);
public const string LookupTableName = nameof(SerializedDictionary<int, int>.LookupTable);
public const int TopHeaderClipHeight = 20;
public const int TopHeaderHeight = 19;
public const int SearchHeaderHeight = 20;
public const int KeyValueHeaderHeight = 18;
public const bool KeyFlag = true;
public const bool ValueFlag = false;
public static readonly Color BorderColor = new Color(36 / 255f, 36 / 255f, 36 / 255f);
public static readonly List<int> NoEntriesList = new List<int>();
internal static GUIContent DisplayTypeToggleContent
{
get
{
if (_displayTypeToggleContent == null)
{
var texture = AssetDatabase.LoadAssetAtPath<Texture>("Assets/Plugins/SerializedCollections/Editor/Assets/BurgerMenu@2x.png");
_displayTypeToggleContent = new GUIContent(texture, "Toggle to either draw existing editor or draw properties manually.");
}
return _displayTypeToggleContent;
}
}
private static GUIContent _displayTypeToggleContent;
private Dictionary<string, SerializedDictionaryInstanceDrawer> _arrayData = new();
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (!_arrayData.ContainsKey(property.propertyPath))
_arrayData.Add(property.propertyPath, new SerializedDictionaryInstanceDrawer(property, fieldInfo));
_arrayData[property.propertyPath].OnGUI(position, label);
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
if (!_arrayData.ContainsKey(property.propertyPath))
_arrayData.Add(property.propertyPath, new SerializedDictionaryInstanceDrawer(property, fieldInfo));
return _arrayData[property.propertyPath].GetPropertyHeight(label);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4ccfc6d910f95ca4f94b294a9a3a8872
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,689 @@
using AYellowpaper.SerializedCollections.Editor.Data;
using AYellowpaper.SerializedCollections.Editor.States;
using AYellowpaper.SerializedCollections.KeysGenerators;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEditorInternal;
using UnityEngine;
namespace AYellowpaper.SerializedCollections.Editor
{
public class SerializedDictionaryInstanceDrawer
{
private FieldInfo _fieldInfo;
private ReorderableList _unexpandedList;
private SingleEditingData _singleEditingData;
private FieldInfo _keyFieldInfo;
private GUIContent _label;
private Rect _totalRect;
private GUIStyle _keyValueStyle;
private SerializedDictionaryAttribute _dictionaryAttribute;
private PropertyData _propertyData;
private bool _propertyListSettingsInitialized = false;
private List<int> _pagedIndices;
private PagingElement _pagingElement;
private int _lastListSize = -1;
private IReadOnlyList<KeyListGeneratorData> _keyGeneratorsWithoutWindow;
private IReadOnlyList<KeyListGeneratorData> _keyGeneratorsWithWindow;
private SearchField _searchField;
private GUIContent _shortDetailsContent;
private GUIContent _detailsContent;
private bool _showSearchBar = false;
private ListState _activeState;
internal ReorderableList ReorderableList { get; private set; }
internal SerializedProperty ListProperty { get; private set; }
internal string SearchText { get; private set; } = string.Empty;
internal SearchListState SearchState { get; private set; }
internal DefaultListState DefaultState { get; private set; }
private class SingleEditingData
{
public bool IsValid => LookupTable != null;
public IKeyable LookupTable;
public void Invalidate()
{
LookupTable = null;
}
}
public SerializedDictionaryInstanceDrawer(SerializedProperty property, FieldInfo fieldInfo)
{
_fieldInfo = fieldInfo;
ListProperty = property.FindPropertyRelative(SerializedDictionaryDrawer.SerializedListName);
_keyValueStyle = new GUIStyle(EditorStyles.toolbarButton);
_keyValueStyle.padding = new RectOffset(0, 0, 0, 0);
_keyValueStyle.border = new RectOffset(0, 0, 0, 0);
_keyValueStyle.alignment = TextAnchor.MiddleCenter;
DefaultState = new DefaultListState(this);
SearchState = new SearchListState(this);
_activeState = DefaultState;
_dictionaryAttribute = _fieldInfo.GetCustomAttribute<SerializedDictionaryAttribute>();
_propertyData = SCEditorUtility.GetPropertyData(ListProperty);
_propertyData.GetElementData(SCEditorUtility.KeyFlag).Settings.DisplayName = _dictionaryAttribute?.KeyName ?? "Key";
_propertyData.GetElementData(SCEditorUtility.ValueFlag).Settings.DisplayName = _dictionaryAttribute?.ValueName ?? "Value";
SavePropertyData();
_pagingElement = new PagingElement();
_pagedIndices = new List<int>();
UpdatePaging();
ReorderableList = MakeList();
_unexpandedList = MakeUnexpandedList();
_searchField = new SearchField();
var listField = _fieldInfo.FieldType.GetField(SerializedDictionaryDrawer.SerializedListName, BindingFlags.Instance | BindingFlags.NonPublic);
var entryType = listField.FieldType.GetGenericArguments()[0];
_keyFieldInfo = entryType.GetField(SerializedDictionaryDrawer.KeyName);
_singleEditingData = new SingleEditingData();
var keyGenerators = KeyListGeneratorCache.GetPopulatorsForType(_keyFieldInfo.FieldType);
_keyGeneratorsWithWindow = keyGenerators.Where(x => x.NeedsWindow).ToList();
_keyGeneratorsWithoutWindow = keyGenerators.Where(x => !x.NeedsWindow).ToList();
UpdateAfterInput();
}
public void OnGUI(Rect position, GUIContent label)
{
_totalRect = position;
_label = new GUIContent(label);
// Only call Update() in EditorWindow contexts where it's required.
// In PropertyDrawers for MonoBehaviours/ScriptableObjects, Unity manages serialization
// and calling Update() interferes with pending edits, causing values to revert.
if (ListProperty.serializedObject.targetObject is EditorWindow)
ListProperty.serializedObject.Update();
EditorGUI.BeginChangeCheck();
DoList(position);
if (EditorGUI.EndChangeCheck())
{
ListProperty.serializedObject.ApplyModifiedProperties();
}
}
public float GetPropertyHeight(GUIContent label)
{
if (!ListProperty.isExpanded)
return SerializedDictionaryDrawer.TopHeaderClipHeight;
return ReorderableList.GetHeight();
}
private void DoList(Rect position)
{
if (ListProperty.isExpanded)
ReorderableList.DoList(position);
else
{
using (new GUI.ClipScope(new Rect(0, position.y, position.width + position.x, SerializedDictionaryDrawer.TopHeaderClipHeight)))
{
_unexpandedList.DoList(position.WithY(0));
}
}
}
private void ProcessState()
{
var newState = _activeState.OnUpdate();
if (newState != null && newState != _activeState)
{
_activeState.OnExit();
_activeState = newState;
newState.OnEnter();
}
}
private SerializedProperty GetElementProperty(SerializedProperty property, bool fieldFlag)
{
return property.FindPropertyRelative(fieldFlag == SerializedDictionaryDrawer.KeyFlag ? SerializedDictionaryDrawer.KeyName : SerializedDictionaryDrawer.ValueName);
}
internal static float CalculateHeightOfElement(SerializedProperty property, bool drawKeyAsList, bool drawValueAsList)
{
SerializedProperty keyProperty = property.FindPropertyRelative(SerializedDictionaryDrawer.KeyName);
SerializedProperty valueProperty = property.FindPropertyRelative(SerializedDictionaryDrawer.ValueName);
return Mathf.Max(SCEditorUtility.CalculateHeight(keyProperty, drawKeyAsList), SCEditorUtility.CalculateHeight(valueProperty, drawValueAsList));
}
private void UpdateAfterInput()
{
InitializeSettingsIfNeeded();
ProcessState();
CheckPaging();
var elementsPerPage = EditorUserSettings.Get().ElementsPerPage;
int pageCount = Mathf.Max(1, Mathf.CeilToInt((float)DefaultState.ListSize / elementsPerPage));
ToggleSearchBar(_propertyData.AlwaysShowSearch ? true : SCEditorUtility.ShouldShowSearch(pageCount));
}
private void InitializeSettingsIfNeeded()
{
void InitializeSettings(bool fieldFlag)
{
var genericArgs = _fieldInfo.FieldType.GetGenericArguments();
var firstProperty = ListProperty.GetArrayElementAtIndex(0);
var keySettings = CreateDisplaySettings(GetElementProperty(firstProperty, fieldFlag), genericArgs[fieldFlag == SCEditorUtility.KeyFlag ? 0 : 1]);
var settings = _propertyData.GetElementData(fieldFlag).Settings;
settings.DisplayType = keySettings.displayType;
settings.HasListDrawerToggle = keySettings.canToggleListDrawer;
}
if (!_propertyListSettingsInitialized && ListProperty.minArraySize > 0)
{
_propertyListSettingsInitialized = true;
InitializeSettings(SCEditorUtility.KeyFlag);
InitializeSettings(SCEditorUtility.ValueFlag);
SavePropertyData();
}
}
private void CheckPaging()
{
// TODO: Is there a better solution to check for Revert/delete/add?
if (_lastListSize != _activeState.ListSize)
{
_lastListSize = _activeState.ListSize;
UpdateSingleEditing();
UpdatePaging();
}
}
private void SavePropertyData()
{
SCEditorUtility.SavePropertyData(ListProperty, _propertyData);
}
private void UpdateSingleEditing()
{
if (ListProperty.serializedObject.isEditingMultipleObjects && _singleEditingData.IsValid)
_singleEditingData.Invalidate();
else if (!ListProperty.serializedObject.isEditingMultipleObjects && !_singleEditingData.IsValid)
{
var dictionary = SCEditorUtility.GetPropertyValue(ListProperty, ListProperty.serializedObject.targetObject);
_singleEditingData.LookupTable = GetLookupTable(dictionary);
}
}
private IKeyable GetLookupTable(object dictionary)
{
var propInfo = dictionary.GetType().GetProperty(SerializedDictionaryDrawer.LookupTableName, BindingFlags.Instance | BindingFlags.NonPublic);
return (IKeyable)propInfo.GetValue(dictionary);
}
private void UpdatePaging()
{
var elementsPerPage = EditorUserSettings.Get().ElementsPerPage;
_pagingElement.PageCount = Mathf.Max(1, Mathf.CeilToInt((float)_activeState.ListSize / elementsPerPage));
_pagedIndices.Clear();
_pagedIndices.Capacity = Mathf.Max(elementsPerPage, _pagedIndices.Capacity);
int startIndex = (_pagingElement.Page - 1) * elementsPerPage;
int endIndex = Mathf.Min(startIndex + elementsPerPage, _activeState.ListSize);
for (int i = startIndex; i < endIndex; i++)
_pagedIndices.Add(i);
string shortDetailsString = (_activeState.ListSize + " " + (_pagedIndices.Count == 1 ? "Element" : "Elements"));
string detailsString = _pagingElement.PageCount > 1
? $"{_pagedIndices[0] + 1}..{_pagedIndices.Last() + 1} / {_activeState.ListSize} Elements"
: shortDetailsString;
_detailsContent = new GUIContent(detailsString);
_shortDetailsContent = new GUIContent(shortDetailsString);
}
private ReorderableList MakeList()
{
var list = new ReorderableList(_pagedIndices, typeof(int), true, true, true, true);
list.onAddCallback += OnAdd;
list.onRemoveCallback += OnRemove;
list.onReorderCallbackWithDetails += OnReorder;
list.drawElementCallback += OnDrawElement;
list.elementHeightCallback += OnGetElementHeight;
list.drawHeaderCallback += OnDrawHeader;
list.drawNoneElementCallback += OnDrawNoneElement;
return list;
}
private ReorderableList MakeUnexpandedList()
{
var list = new ReorderableList(SerializedDictionaryDrawer.NoEntriesList, typeof(int));
list.drawHeaderCallback = DrawUnexpandedHeader;
return list;
}
private void ToggleSearchBar(bool flag)
{
_showSearchBar = flag;
ReorderableList.headerHeight = SerializedDictionaryDrawer.TopHeaderClipHeight + SerializedDictionaryDrawer.KeyValueHeaderHeight + (_showSearchBar ? SerializedDictionaryDrawer.SearchHeaderHeight : 0);
if (!_showSearchBar)
{
if (_searchField.HasFocus())
GUI.FocusControl(null);
SearchText = string.Empty;
}
}
private void OnDrawNoneElement(Rect rect)
{
EditorGUI.LabelField(rect, EditorGUIUtility.TrTextContent(_activeState.NoElementsText));
}
private (DisplayType displayType, bool canToggleListDrawer) CreateDisplaySettings(SerializedProperty property, Type type)
{
bool hasCustomEditor = SCEditorUtility.HasDrawerForType(type, property.propertyType == SerializedPropertyType.ManagedReference);
bool isGenericWithChildren = property.propertyType == SerializedPropertyType.Generic && property.hasVisibleChildren;
bool isArray = property.isArray && property.propertyType != SerializedPropertyType.String;
bool canToggleListDrawer = isArray || (isGenericWithChildren && hasCustomEditor);
DisplayType displayType = DisplayType.PropertyNoLabel;
if (canToggleListDrawer)
displayType = DisplayType.Property;
else if (!isArray && isGenericWithChildren && !hasCustomEditor)
displayType = DisplayType.List;
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();
_pagingElement.OnGUI(rect);
if (EditorGUI.EndChangeCheck())
{
ReorderableList.ClearSelection();
UpdatePaging();
}
}
private void OnDrawHeader(Rect rect)
{
Rect topRect = rect.WithHeight(SerializedDictionaryDrawer.TopHeaderHeight);
Rect adjustedTopRect = topRect.WithXAndWidth(_totalRect.x + 1, _totalRect.width - 1);
DoMainHeader(adjustedTopRect.CutLeft(topRect.x - adjustedTopRect.x));
if (_showSearchBar)
{
adjustedTopRect = adjustedTopRect.AppendDown(SerializedDictionaryDrawer.SearchHeaderHeight);
DoSearch(adjustedTopRect);
}
DoKeyValueRect(adjustedTopRect.AppendDown(SerializedDictionaryDrawer.KeyValueHeaderHeight));
UpdateAfterInput();
}
private void DoMainHeader(Rect rect)
{
Rect lastTopRect = rect.AppendRight(0).WithHeight(EditorGUIUtility.singleLineHeight);
lastTopRect = lastTopRect.AppendLeft(20);
DoOptionsButton(lastTopRect);
lastTopRect = lastTopRect.AppendLeft(5);
if (_pagingElement.PageCount > 1)
{
lastTopRect = lastTopRect.AppendLeft(_pagingElement.GetDesiredWidth());
DoPaging(lastTopRect);
}
var detailsStyle = EditorStyles.miniLabel;
lastTopRect = lastTopRect.AppendLeft(detailsStyle.CalcSize(_detailsContent).x, 5);
GUI.Label(lastTopRect, _detailsContent, detailsStyle);
if (!_singleEditingData.IsValid)
{
lastTopRect = lastTopRect.AppendLeft(lastTopRect.height + 5);
var guicontent = EditorGUIUtility.TrIconContent(EditorGUIUtility.Load("d_console.infoicon") as Texture, "Conflict checking, duplicate key removal and populators not supported in multi object editing mode.");
GUI.Label(lastTopRect, guicontent);
}
EditorGUI.BeginProperty(rect, _label, ListProperty);
ListProperty.isExpanded = EditorGUI.Foldout(rect.WithXAndWidth(rect.x - 5, lastTopRect.x - rect.x), ListProperty.isExpanded, _label, true);
EditorGUI.EndProperty();
}
private void DoOptionsButton(Rect rect)
{
var screenRect = GUIUtility.GUIToScreenRect(rect);
if (GUI.Button(rect, EditorGUIUtility.IconContent("pane options@2x"), EditorStyles.iconButton))
{
var gm = new GenericMenu();
SCEditorUtility.AddGenericMenuItem(gm, false, ListProperty.minArraySize > 0, new GUIContent("Clear"), () => QueueAction(ClearList));
SCEditorUtility.AddGenericMenuItem(gm, false, true, new GUIContent("Remove Conflicts"), () => QueueAction(RemoveConflicts));
SCEditorUtility.AddGenericMenuItem(gm, false, _keyGeneratorsWithWindow.Count > 0, new GUIContent("Bulk Edit..."), () => OpenKeysGeneratorSelectorWindow(screenRect));
if (_keyGeneratorsWithoutWindow.Count > 0)
{
gm.AddSeparator(string.Empty);
foreach (var generatorData in _keyGeneratorsWithoutWindow)
{
SCEditorUtility.AddGenericMenuItem(gm, false, true, new GUIContent(generatorData.Name), OnPopulatorDataSelected, generatorData);
}
}
gm.AddSeparator(string.Empty);
SCEditorUtility.AddGenericMenuItem(gm, _propertyData.AlwaysShowSearch, true, new GUIContent("Always Show Search"), ToggleAlwaysShowSearchPropertyData);
gm.AddItem(new GUIContent("Preferences..."), false, () => SettingsService.OpenUserPreferences(EditorUserSettingsProvider.PreferencesPath));
gm.DropDown(rect);
}
}
private void OnPopulatorDataSelected(object userData)
{
var data = (KeyListGeneratorData)userData;
var so = (KeyListGenerator)ScriptableObject.CreateInstance(data.GeneratorType);
so.hideFlags = HideFlags.DontSave;
ApplyPopulatorQueued(so, ModificationType.Add);
}
private void OpenKeysGeneratorSelectorWindow(Rect rect)
{
var window = ScriptableObject.CreateInstance<KeyListGeneratorSelectorWindow>();
window.Initialize(_keyGeneratorsWithWindow, _keyFieldInfo.FieldType);
window.ShowAsDropDown(rect, new Vector2(400, 200));
window.OnApply += ApplyPopulatorQueued;
}
private void ToggleAlwaysShowSearchPropertyData()
{
_propertyData.AlwaysShowSearch = !_propertyData.AlwaysShowSearch;
SavePropertyData();
}
private void DoKeyValueRect(Rect rect)
{
float width = EditorGUIUtility.labelWidth + 22;
Rect leftRect = rect.WithWidth(width);
Rect rightRect = leftRect.AppendRight(rect.width - width);
if (Event.current.type == EventType.Repaint && _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 (ListProperty.minArraySize > 0)
{
DoDisplayTypeToggle(leftRect, SerializedDictionaryDrawer.KeyFlag);
DoDisplayTypeToggle(rightRect, SerializedDictionaryDrawer.ValueFlag);
}
EditorGUI.DrawRect(rect.AppendDown(1, -1), SerializedDictionaryDrawer.BorderColor);
}
private void DoSearch(Rect rect)
{
EditorGUI.DrawRect(rect.AppendLeft(1), SerializedDictionaryDrawer.BorderColor);
EditorGUI.DrawRect(rect.AppendRight(1, -1), SerializedDictionaryDrawer.BorderColor);
EditorGUI.DrawRect(rect.AppendDown(1, -1), SerializedDictionaryDrawer.BorderColor);
SearchText = _searchField.OnToolbarGUI(rect.CutTop(2).CutHorizontal(6), SearchText);
}
private void ApplyPopulatorQueued(KeyListGenerator populator, ModificationType modificationType)
{
var array = populator.GetKeys(_keyFieldInfo.FieldType).OfType<object>().ToArray();
QueueAction(() => ApplyPopulator(array, modificationType));
}
private void QueueAction(EditorApplication.CallbackFunction action)
{
EditorApplication.delayCall += action;
}
private void ApplyPopulator(IEnumerable<object> elements, ModificationType modificationType)
{
foreach (var targetObject in ListProperty.serializedObject.targetObjects)
{
Undo.RecordObject(targetObject, "Populate");
var dictionary = SCEditorUtility.GetPropertyValue(ListProperty, targetObject);
var lookupTable = GetLookupTable(dictionary);
if (modificationType == ModificationType.Add)
AddElements(lookupTable, elements);
else if (modificationType == ModificationType.Remove)
RemoveElements(lookupTable, elements);
else if (modificationType == ModificationType.Confine)
ConfineElements(lookupTable, elements);
lookupTable.RecalculateOccurences();
PrefabUtility.RecordPrefabInstancePropertyModifications(targetObject);
}
ListProperty.serializedObject.Update();
ActiveEditorTracker.sharedTracker.ForceRebuild();
}
private static void AddElements(IKeyable lookupTable, IEnumerable<object> elements)
{
foreach (var key in elements)
{
var occurences = lookupTable.GetOccurences(key);
if (occurences.Count > 0)
continue;
lookupTable.AddKey(key);
}
}
private static void ConfineElements(IKeyable lookupTable, IEnumerable<object> elements)
{
var keysToRemove = lookupTable.Keys.OfType<object>().ToHashSet();
foreach (var key in elements)
keysToRemove.Remove(key);
RemoveElements(lookupTable, keysToRemove);
}
private static void RemoveElements(IKeyable lookupTable, IEnumerable<object> elements)
{
var indicesToRemove = elements.SelectMany(x => lookupTable.GetOccurences(x)).OrderByDescending(index => index);
foreach (var index in indicesToRemove)
{
lookupTable.RemoveAt(index);
}
}
private void ClearList()
{
ListProperty.ClearArray();
ListProperty.serializedObject.ApplyModifiedProperties();
}
private void RemoveConflicts()
{
foreach (var targetObject in ListProperty.serializedObject.targetObjects)
{
Undo.RecordObject(targetObject, "Remove Conflicts");
var dictionary = SCEditorUtility.GetPropertyValue(ListProperty, targetObject);
var lookupTable = GetLookupTable(dictionary);
List<int> duplicateIndices = new List<int>();
foreach (var key in lookupTable.Keys)
{
var occurences = lookupTable.GetOccurences(key);
for (int i = 1; i < occurences.Count; i++)
duplicateIndices.Add(occurences[i]);
}
foreach (var indexToRemove in duplicateIndices.OrderByDescending(x => x))
{
lookupTable.RemoveAt(indexToRemove);
}
lookupTable.RecalculateOccurences();
PrefabUtility.RecordPrefabInstancePropertyModifications(targetObject);
}
ListProperty.serializedObject.Update();
ActiveEditorTracker.sharedTracker.ForceRebuild();
}
private void DoDisplayTypeToggle(Rect contentRect, bool fieldFlag)
{
var displayData = _propertyData.GetElementData(fieldFlag);
if (displayData.Settings.HasListDrawerToggle)
{
Rect rightRectToggle = new Rect(contentRect);
rightRectToggle.x += rightRectToggle.width - 18;
rightRectToggle.width = 18;
EditorGUI.BeginChangeCheck();
bool newValue = GUI.Toggle(rightRectToggle, displayData.IsListToggleActive, SerializedDictionaryDrawer.DisplayTypeToggleContent, EditorStyles.toolbarButton);
if (EditorGUI.EndChangeCheck())
{
displayData.IsListToggleActive = newValue;
SavePropertyData();
}
}
}
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);
}
private void OnDrawElement(Rect rect, int index, bool isActive, bool isFocused)
{
const int lineLeftSpace = 2;
const int lineWidth = 1;
const int lineRightSpace = 12;
const int totalSpace = lineLeftSpace + lineWidth + lineRightSpace;
int actualIndex = _pagedIndices[index];
SerializedProperty kvp = _activeState.GetPropertyAtIndex(actualIndex);
Rect keyRect = rect.WithSize(EditorGUIUtility.labelWidth - 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);
var keyProperty = kvp.FindPropertyRelative(SerializedDictionaryDrawer.KeyName);
var valueProperty = kvp.FindPropertyRelative(SerializedDictionaryDrawer.ValueName);
Color prevColor = GUI.color;
if (_singleEditingData.IsValid)
{
var keyObject = _keyFieldInfo.GetValue(_singleEditingData.LookupTable.GetKeyAt(actualIndex));
var occurences = _singleEditingData.LookupTable.GetOccurences(keyObject);
if (occurences.Count > 1)
{
GUI.color = occurences[0] == actualIndex ? Color.yellow : Color.red;
}
if (!SerializedCollectionsUtility.IsValidKey(keyObject))
{
GUI.color = Color.red;
}
}
var keyDisplayData = _propertyData.GetElementData(SerializedDictionaryDrawer.KeyFlag);
DrawGroupedElement(keyRect, 20, keyProperty, keyDisplayData.EffectiveDisplayType);
EditorGUI.DrawRect(lineRect, new Color(36 / 255f, 36 / 255f, 36 / 255f));
GUI.color = prevColor;
var valueDisplayData = _propertyData.GetElementData(SerializedDictionaryDrawer.ValueFlag);
DrawGroupedElement(valueRect, lineRightSpace, valueProperty, valueDisplayData.EffectiveDisplayType);
}
private void DrawGroupedElement(Rect rect, int spaceForProperty, SerializedProperty property, DisplayType displayType)
{
using (new LabelWidth(rect.width * 0.4f))
{
float height = SCEditorUtility.CalculateHeight(property.Copy(), displayType);
Rect groupRect = rect.CutLeft(-spaceForProperty).WithHeight(height);
GUI.BeginGroup(groupRect);
Rect elementRect = new Rect(spaceForProperty, 0, rect.width, height);
_activeState.DrawElement(elementRect, property, displayType);
DrawInvisibleProperty(rect.WithWidth(spaceForProperty), property);
GUI.EndGroup();
}
}
internal static void DrawInvisibleProperty(Rect rect, SerializedProperty property)
{
const int propertyOffset = 5;
GUI.BeginClip(rect.CutLeft(-propertyOffset));
EditorGUI.BeginProperty(rect, GUIContent.none, property);
EditorGUI.EndProperty();
GUI.EndClip();
}
internal static void DrawElement(Rect rect, SerializedProperty property, DisplayType displayType, Action<SerializedProperty> BeforeDrawingCallback = null, Action<SerializedProperty> AfterDrawingCallback = null)
{
switch (displayType)
{
case DisplayType.Property:
BeforeDrawingCallback?.Invoke(property);
EditorGUI.PropertyField(rect, property, true);
AfterDrawingCallback?.Invoke(property);
break;
case DisplayType.PropertyNoLabel:
BeforeDrawingCallback?.Invoke(property);
EditorGUI.PropertyField(rect, property, GUIContent.none, true);
AfterDrawingCallback?.Invoke(property);
break;
case DisplayType.List:
Rect childRect = rect.WithHeight(0);
foreach (SerializedProperty prop in SCEditorUtility.GetChildren(property.Copy()))
{
childRect = childRect.AppendDown(EditorGUI.GetPropertyHeight(prop, true));
BeforeDrawingCallback?.Invoke(prop);
EditorGUI.PropertyField(childRect, prop, true);
AfterDrawingCallback?.Invoke(prop);
}
break;
default:
break;
}
}
private void OnAdd(ReorderableList list)
{
int targetIndex = list.selectedIndices.Count > 0 && list.selectedIndices[0] >= 0 ? list.selectedIndices[0] : 0;
int actualTargetIndex = targetIndex < _pagedIndices.Count ? _pagedIndices[targetIndex] : 0;
_activeState.InserElementAt(actualTargetIndex);
}
private void OnReorder(ReorderableList list, int oldIndex, int newIndex)
{
UpdatePaging();
ListProperty.MoveArrayElement(_pagedIndices[oldIndex], _pagedIndices[newIndex]);
}
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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9c3b210db82056a4a93c337b45da413b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7691132b92786be418f91bf39639db87
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,60 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using System;
namespace AYellowpaper.SerializedCollections.Editor
{
public sealed class EditorUserSettings : ScriptableObject
{
[SerializeField]
private bool _alwaysShowSearch = false;
[SerializeField, Range(1, 10)]
private int _pageCountForSearch = 1;
[SerializeField, Min(1)]
private int _elementsPerPage = 10;
public bool AlwaysShowSearch => _alwaysShowSearch;
public int PageCountForSearch => _pageCountForSearch;
public int ElementsPerPage => _elementsPerPage;
private static EditorUserSettings _instance;
private const string _filePath = "UserSettings/SerializedCollectionsEditorSettings.asset";
public static EditorUserSettings Get()
{
if (_instance == null)
{
_instance = CreateInstance<EditorUserSettings>();
LoadInto(_instance);
}
return _instance;
}
private static void LoadInto(EditorUserSettings settings)
{
if (!File.Exists(_filePath)) return;
try
{
string json = File.ReadAllText(_filePath);
EditorJsonUtility.FromJsonOverwrite(json, settings);
return;
}
catch (Exception e)
{
Debug.LogError(e);
return;
}
}
internal static void Save()
{
string contents = EditorJsonUtility.ToJson(Get());
File.WriteAllText(_filePath, contents);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 21353c5f0548cba4a98029e0c433a0dc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,82 @@
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.AnimatedValues;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UIElements;
namespace AYellowpaper.SerializedCollections.Editor
{
public class EditorUserSettingsProvider : SettingsProvider
{
public const string PreferencesPath = "Preferences/Serialized Collections";
private SerializedObject _serializedObject;
private SerializedProperty _alwaysShowSearch;
private SerializedProperty _pageCountForSearch;
private SerializedProperty _elementsPerPage;
private AnimBool _searchAnimBool;
class Styles
{
}
[SettingsProvider]
public static SettingsProvider CreateProvider()
{
var provider = new EditorUserSettingsProvider(PreferencesPath, SettingsScope.User);
provider.keywords = GetSearchKeywordsFromGUIContentProperties<Styles>();
return provider;
}
public EditorUserSettingsProvider(string path, SettingsScope scope = SettingsScope.User) : base(path, scope) { }
public static bool IsSettingsAvailable() => EditorUserSettings.Get() != null;
public override void OnActivate(string searchContext, VisualElement rootElement)
{
EnsureSerializedObjectExists();
}
private void EnsureSerializedObjectExists()
{
if (_serializedObject == null)
{
_searchAnimBool = new AnimBool();
_searchAnimBool.valueChanged.AddListener(new UnityAction(Repaint));
_serializedObject = new SerializedObject(EditorUserSettings.Get());
_alwaysShowSearch = _serializedObject.FindProperty("_alwaysShowSearch");
_pageCountForSearch = _serializedObject.FindProperty("_pageCountForSearch");
_elementsPerPage = _serializedObject.FindProperty("_elementsPerPage");
}
}
public override void OnGUI(string searchContext)
{
EnsureSerializedObjectExists();
EditorGUI.indentLevel = 1;
_serializedObject.UpdateIfRequiredOrScript();
EditorGUILayout.PropertyField(_alwaysShowSearch);
_searchAnimBool.target = !_alwaysShowSearch.boolValue;
using (var group = new EditorGUILayout.FadeGroupScope(_searchAnimBool.faded))
{
if (group.visible)
{
EditorGUILayout.PropertyField(_pageCountForSearch);
}
}
EditorGUILayout.PropertyField(_elementsPerPage);
bool changed =_serializedObject.ApplyModifiedProperties();
if (changed)
{
EditorUserSettings.Save();
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 01b95baf6e99cff43a84c4ba42557db4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b07f70f96e8585f45976044096080ecd
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,55 @@
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using static AYellowpaper.SerializedCollections.Editor.SerializedDictionaryDrawer;
namespace AYellowpaper.SerializedCollections.Editor.States
{
internal class DefaultListState : ListState
{
public override int ListSize => Drawer.ListProperty.minArraySize;
public DefaultListState(SerializedDictionaryInstanceDrawer serializedDictionaryDrawer) : base(serializedDictionaryDrawer)
{
}
public override void OnEnter()
{
Drawer.ReorderableList.draggable = true;
}
public override void OnExit()
{
}
public override ListState OnUpdate()
{
if (Drawer.SearchText.Length > 0)
return Drawer.SearchState;
return this;
}
public override void DrawElement(Rect rect, SerializedProperty property, DisplayType displayType)
{
SerializedDictionaryInstanceDrawer.DrawElement(rect, property, displayType);
}
public override SerializedProperty GetPropertyAtIndex(int index)
{
return Drawer.ListProperty.GetArrayElementAtIndex(index);
}
public override void RemoveElementAt(int index)
{
Drawer.ListProperty.DeleteArrayElementAtIndex(index);
}
public override void InserElementAt(int index)
{
Drawer.ListProperty.InsertArrayElementAtIndex(index);
Drawer.ListProperty.serializedObject.ApplyModifiedProperties();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 803188ced5ea71c41b6079f7ae375573
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,34 @@
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using static AYellowpaper.SerializedCollections.Editor.SerializedDictionaryDrawer;
namespace AYellowpaper.SerializedCollections.Editor.States
{
internal abstract class ListState
{
public abstract int ListSize { get; }
public virtual string NoElementsText => "List is Empty.";
public readonly SerializedDictionaryInstanceDrawer Drawer;
public ListState(SerializedDictionaryInstanceDrawer serializedDictionaryDrawer)
{
Drawer = serializedDictionaryDrawer;
}
public abstract SerializedProperty GetPropertyAtIndex(int index);
public abstract ListState OnUpdate();
public abstract void OnEnter();
public abstract void OnExit();
public abstract void DrawElement(Rect rect, SerializedProperty property, DisplayType displayType);
public abstract void RemoveElementAt(int index);
public abstract void InserElementAt(int index);
public virtual float GetHeightAtIndex(int index, bool drawKeyAsList, bool drawValueAsList)
{
return SerializedDictionaryInstanceDrawer.CalculateHeightOfElement(GetPropertyAtIndex(index), drawKeyAsList, drawValueAsList);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4b4752173882f944eb16a5720a22f3e9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,109 @@
using AYellowpaper.SerializedCollections.Editor.Search;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using System.Linq;
using System;
using static AYellowpaper.SerializedCollections.Editor.SerializedDictionaryDrawer;
namespace AYellowpaper.SerializedCollections.Editor.States
{
internal class SearchListState : ListState
{
public override int ListSize => _searchResults.Count;
public override string NoElementsText => "No Results";
public bool OnlyShowMatchingValues { get; set; }
private string _lastSearch = string.Empty;
private List<SearchResultEntry> _searchResults = new List<SearchResultEntry>();
private HashSet<string> _foundProperties;
private Color _previousColor;
public SearchListState(SerializedDictionaryInstanceDrawer serializedDictionaryDrawer) : base(serializedDictionaryDrawer)
{
}
public override void DrawElement(Rect rect, SerializedProperty property, DisplayType displayType)
{
SerializedDictionaryInstanceDrawer.DrawElement(rect, property, displayType, BeforeDrawingProperty, AfterDrawingProperty);
}
private void BeforeDrawingProperty(SerializedProperty obj)
{
_previousColor = GUI.backgroundColor;
if (_foundProperties.Contains(obj.propertyPath))
{
GUI.backgroundColor = Color.blue;
}
}
private void AfterDrawingProperty(SerializedProperty obj)
{
GUI.backgroundColor = _previousColor;
}
public override void OnEnter()
{
Drawer.ReorderableList.draggable = false;
UpdateSearch();
}
public override void OnExit()
{
}
public override ListState OnUpdate()
{
if (Drawer.SearchText.Length == 0)
return Drawer.DefaultState;
UpdateSearch();
return this;
}
private void UpdateSearch()
{
if (_lastSearch != Drawer.SearchText)
{
_lastSearch = Drawer.SearchText;
PerformSearch(Drawer.SearchText);
}
}
public void PerformSearch(string searchString)
{
var query = new SearchQuery(Matchers.RegisteredMatchers);
query.SearchString = searchString;
_searchResults.Clear();
_searchResults.AddRange(query.ApplyToArrayProperty(Drawer.ListProperty));
_foundProperties = _searchResults.SelectMany(x => x.MatchingResults, (x, y) => y.Property.propertyPath).ToHashSet();
}
public override SerializedProperty GetPropertyAtIndex(int index)
{
return _searchResults[index].Property;
}
public override float GetHeightAtIndex(int index, bool drawKeyAsList, bool drawValueAsList)
{
return base.GetHeightAtIndex(index, drawKeyAsList, drawValueAsList);
}
public override void RemoveElementAt(int index)
{
var indexToDelete = _searchResults[index].Index;
Drawer.ListProperty.DeleteArrayElementAtIndex(indexToDelete);
PerformSearch(_lastSearch);
}
public override void InserElementAt(int index)
{
var indexToAdd = _searchResults[index].Index;
Drawer.ListProperty.InsertArrayElementAtIndex(indexToAdd);
PerformSearch(_lastSearch);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1ed2b1abf79f0c74c8224a423ec3123c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9ce09595c06b9854abdd9a22a57e16bd
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace AYellowpaper.SerializedCollections.Editor
{
public struct GUIEnabledScope : IDisposable
{
public readonly bool PreviouslyEnabled;
public GUIEnabledScope(bool enabled)
{
PreviouslyEnabled = GUI.enabled;
GUI.enabled = enabled;
}
public void Dispose()
{
GUI.enabled = PreviouslyEnabled;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c671c12e9de22d042be004c62b6f4158
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace AYellowpaper.SerializedCollections.Editor
{
public struct LabelWidth : IDisposable
{
public float PreviousWidth { get; }
public LabelWidth(float width)
{
PreviousWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = width;
}
public void Dispose()
{
EditorGUIUtility.labelWidth = PreviousWidth;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fd75bc249daa39f4e832a3dab9c23c82
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,41 @@
using UnityEngine;
namespace AYellowpaper.SerializedCollections.Editor
{
public static class RectUtility
{
public static Rect WithX(this Rect rect, float x) => new Rect(x, rect.y, rect.width, rect.height);
public static Rect WithY(this Rect rect, float y) => new Rect(rect.x, y, rect.width, rect.height);
public static Rect WithWidth(this Rect rect, float width) => new Rect(rect.x, rect.y, width, rect.height);
public static Rect WithHeight(this Rect rect, float height) => new Rect(rect.x, rect.y, rect.width, height);
public static Rect WithPosition(this Rect rect, Vector2 position) => new Rect(position, rect.size);
public static Rect WithPosition(this Rect rect, float x, float y) => new Rect(new Vector2(x, y), rect.size);
public static Rect WithSize(this Rect rect, Vector2 size) => new Rect(rect.position, size);
public static Rect WithSize(this Rect rect, float width, float height) => new Rect(rect.position, new Vector2(width, height));
public static Rect WithXAndWidth(this Rect rect, float x, float width) => new Rect(x, rect.y, width, rect.height);
public static Rect WithYAndHeight(this Rect rect, float y, float height) => new Rect(rect.x, y, rect.width, height);
public static Rect AppendRight(this Rect rect, float width) => new Rect(rect.x + rect.width, rect.y, width, rect.height);
public static Rect AppendRight(this Rect rect, float width, float space) => new Rect(rect.x + rect.width + space, rect.y, width, rect.height);
public static Rect AppendLeft(this Rect rect, float width) => new Rect(rect.x - width, rect.y, width, rect.height);
public static Rect AppendLeft(this Rect rect, float width, float space) => new Rect(rect.x - space - width, rect.y, width, rect.height);
public static Rect AppendUp(this Rect rect, float height) => new Rect(rect.x, rect.y - height, rect.width, height);
public static Rect AppendUp(this Rect rect, float height, float space) => new Rect(rect.x, rect.y - space - height, rect.width, height);
public static Rect AppendDown(this Rect rect, float height) => new Rect(rect.x, rect.y + rect.height, rect.width, height);
public static Rect AppendDown(this Rect rect, float height, float space) => new Rect(rect.x, rect.y + rect.height + space, rect.width, height);
public static Rect CutLeft(this Rect rect, float width) => new Rect(rect.x + width, rect.y, rect.width - width, rect.height);
public static Rect CutRight(this Rect rect, float width) => new Rect(rect.x, rect.y, rect.width - width, rect.height);
public static Rect CutTop(this Rect rect, float height) => new Rect(rect.x, rect.y + height, rect.width, rect.height - height);
public static Rect CutBottom(this Rect rect, float height) => new Rect(rect.x, rect.y, rect.width, rect.height - height);
public static Rect CutHorizontal(this Rect rect, float leftAndRight) => CutHorizontal(rect, leftAndRight, leftAndRight);
public static Rect CutHorizontal(this Rect rect, float left, float right) => new Rect(rect.x + left, rect.y, rect.width - left - right, rect.height);
public static Rect CutVertical(this Rect rect, float topAndBottom) => CutVertical(rect, topAndBottom, topAndBottom);
public static Rect CutVertical(this Rect rect, float top, float bottom) => new Rect(rect.x, rect.y + top, rect.width, rect.height - top - bottom);
public static Rect Cut(this Rect rect, float topBottom, float leftRight) => Cut(rect, topBottom, leftRight, topBottom, leftRight);
public static Rect Cut(this Rect rect, float top, float right, float bottom, float left) => new Rect(rect.x + left, rect.y + top, rect.width - left - right, rect.height - top - bottom);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 68261ebef89d61441a35961731b0a083
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,201 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using System.Linq;
using AYellowpaper.SerializedCollections.Editor.Data;
using UnityEngine;
using System.Collections;
namespace AYellowpaper.SerializedCollections.Editor
{
internal static class SCEditorUtility
{
public const string EditorPrefsPrefix = "SC_";
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);
}
public static float CalculateHeight(SerializedProperty property, bool drawAsList)
{
if (drawAsList)
{
float height = 0;
foreach (SerializedProperty child in GetChildren(property))
height += EditorGUI.GetPropertyHeight(child, true);
return height;
}
return EditorGUI.GetPropertyHeight(property, true);
}
public static IEnumerable<SerializedProperty> GetChildren(SerializedProperty property, bool recursive = false)
{
if (!property.hasVisibleChildren)
{
yield return property;
yield break;
}
SerializedProperty end = property.GetEndProperty();
property.NextVisible(true);
do
{
yield return property;
} 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();
var json = EditorPrefs.GetString(EditorPrefsPrefix + property.propertyPath, null);
if (json != null)
EditorJsonUtility.FromJsonOverwrite(json, data);
return data;
}
public static void SavePropertyData(SerializedProperty property, PropertyData propertyData)
{
var json = EditorJsonUtility.ToJson(propertyData);
EditorPrefs.SetString(EditorPrefsPrefix + property.propertyPath, json);
}
public static bool ShouldShowSearch(int pages)
{
var settings = EditorUserSettings.Get();
return settings.AlwaysShowSearch ? true : pages >= settings.PageCountForSearch;
}
public static bool HasDrawerForType(Type type, bool isPropertyManagedReferenceType)
{
Type attributeUtilityType = typeof(SerializedProperty).Assembly.GetType("UnityEditor.ScriptAttributeUtility");
if (attributeUtilityType == null)
return false;
var getDrawerMethod = attributeUtilityType.GetMethod("GetDrawerTypeForType", BindingFlags.Static | BindingFlags.NonPublic);
if (getDrawerMethod == null)
return false;
#if UNITY_2022_3_OR_NEWER
if (getDrawerMethod.GetParameters().Length == 2)
{
return getDrawerMethod.Invoke(type, new object[] { type, isPropertyManagedReferenceType }) != null;
} else if(getDrawerMethod.GetParameters().Length == 3)
{
return getDrawerMethod.Invoke(type, new object[] { type, new Type[0], isPropertyManagedReferenceType }) != null;
}
else
{
return getDrawerMethod.Invoke(type, new object[] { type }) != null;
}
#else
return getDrawerMethod.Invoke(type, new object[] { type }) != null;
#endif
}
internal static void AddGenericMenuItem(GenericMenu genericMenu, bool isOn, bool isEnabled, GUIContent content, GenericMenu.MenuFunction action)
{
if (isEnabled)
genericMenu.AddItem(content, isOn, action);
else
genericMenu.AddDisabledItem(content);
}
internal static void AddGenericMenuItem(GenericMenu genericMenu, bool isOn, bool isEnabled, GUIContent content, GenericMenu.MenuFunction2 action, object userData)
{
if (isEnabled)
genericMenu.AddItem(content, isOn, action, userData);
else
genericMenu.AddDisabledItem(content);
}
internal static bool TryGetTypeFromProperty(SerializedProperty property, out Type type)
{
try
{
var classType = typeof(EditorGUI).Assembly.GetType("UnityEditor.ScriptAttributeUtility");
var methodInfo = classType.GetMethod("GetFieldInfoFromProperty", BindingFlags.Static | BindingFlags.NonPublic);
var parameters = new object[] { property, null };
methodInfo.Invoke(null, parameters);
type = (Type) parameters[1];
return true;
}
catch
{
type = null;
return false;
}
}
public static object GetPropertyValue(SerializedProperty prop, object target)
{
var path = prop.propertyPath.Replace(".Array.data[", "[");
var elements = path.Split('.');
foreach (var element in elements.Take(elements.Length - 1))
{
if (element.Contains("["))
{
var elementName = element.Substring(0, element.IndexOf("["));
var index = Convert.ToInt32(element.Substring(element.IndexOf("[")).Replace("[", "").Replace("]", ""));
target = GetValue(target, elementName, index);
}
else
{
target = GetValue(target, element);
}
}
return target;
}
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);
if (f == null)
{
var p = type.GetProperty(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (p == null)
return null;
return p.GetValue(source, null);
}
return f.GetValue(source);
}
public static object GetValue(object source, string name, int index)
{
var enumerable = GetValue(source, name) as IEnumerable;
var enm = enumerable.GetEnumerator();
while (index-- >= 0)
enm.MoveNext();
return enm.Current;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 138277cf2e7d2cd4e99c3cd7e2ecaaed
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,98 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace AYellowpaper.SerializedCollections.Editor
{
internal static class SCEnumUtility
{
private static Dictionary<Type, EnumCache> _cache = new Dictionary<Type, EnumCache>();
internal static EnumCache GetEnumCache(Type enumType)
{
if (_cache.TryGetValue(enumType, out var val))
return val;
try
{
var classType = typeof(EditorGUI).Assembly.GetType("UnityEditor.EnumDataUtility");
var methodInfo = classType.GetMethod("GetCachedEnumData", BindingFlags.Static | BindingFlags.NonPublic);
var parameters = new object[] { enumType, true };
var result = methodInfo.Invoke(null, parameters);
var flagValues = (int[])result.GetType().GetField("flagValues").GetValue(result);
var names = (string[])result.GetType().GetField("names").GetValue(result);
var cache = new EnumCache(enumType, flagValues, names);
_cache.Add(enumType, cache);
return cache;
}
catch
{
throw;
}
}
}
internal record EnumCache
{
public readonly Type Type;
public readonly bool IsFlag;
public readonly int Length;
public readonly int[] FlagValues;
public readonly string[] Names;
private readonly Dictionary<int, string[]> _namesByValue = new Dictionary<int, string[]>();
public EnumCache(Type type, int[] flagValues, string[] displayNames)
{
Type = type;
FlagValues = flagValues;
Names = displayNames;
Length = flagValues.Length;
IsFlag = Type.IsDefined(typeof(FlagsAttribute));
}
internal string[] GetNamesForValue(int value)
{
if (_namesByValue.TryGetValue(value, out var list))
return list;
string[] array = IsFlag ? GetFlagValues(value).ToArray() : new[] { GetEnumValue(value) };
_namesByValue.Add(value, array);
return array;
}
private string GetEnumValue(int value)
{
for (int i = 0; i < Length; i++)
{
if (FlagValues[i] == value)
return Names[i];
}
return null;
}
private IEnumerable<string> GetFlagValues(int flagValue)
{
if (flagValue == 0)
{
yield return FlagValues[0] == 0 ? Names[0] : "Nothing";
yield break;
}
for (int i = 0; i < Length; i++)
{
int fv = FlagValues[i];
if ((fv & flagValue) == fv && fv != 0)
yield return Names[i];
}
if (FlagValues[Length - 1] != -1 && flagValue == -1)
yield return "Everything";
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 76def6672c756704bac4efd5a3625113
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,7 @@
Copyright (c) 2023 ayellowpaper
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 0c686c48b6dd7004fbba03d7faefcb58
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,83 @@
# Serialized Dictionary
Serialized Dictionary is designed to feel native to the Unity Editor while providing some additional functionality to speed up frequent workflows.
## Quick Start
Use the class `SerializedDictionary<,>` in the Namespace `AYellowpaper.SerializedCollections` instead of the `Dictionary<,>` class to serialize your data. Use the `SerializedDictionary` Attribute for further customization. It follows the same Unity serialization rules as other Unity types.
```csharp
[SerializedDictionary("Damage Type", "Description")]
public SerializedDictionary<DamageType, string> ElementDescriptions;
```
## User Guide
Serialized Dictionary will serialized any Unity serializable type, including Unity Objects like transforms and ScriptableObjects. Furthermore, it allows to serialize duplicate keys and null values. The main purpose is to avoid accidental loss of data when you decide to change code or remove objects. The following color coding exists:
- **Red**: The key is invalid, meaning either duplicate or null
- **Yellow**: There are duplicate keys, but this is the one that's used (it comes before others)
- **Blue**: The key was found in the search
The Burger Menu in the top right is very important. It contains important options that will speed up your workflow. Most of the should be self explanatory.
![Menu options](./.images/menu.png)
## Bulk Edit Operations
To quickly modify lots of existing entries you can use and also create custom `KeyListGenerators`. E.g. for dictionaries that contain enums as keys, theres a `KeyListGenerator` that will populate the dictionary with all values from the enum with one press of a button.
1. Select "Populate Enum" with the dictionary that has enum as key
![Menu options](./.images/populate.png)
2. The dictionary is filled with all values from the enum
![Menu options](./.images/populated.png)
Furthermore, there are populators for integers, which allow for custom input fields to modify the data that will be generated.
![Menu options](./.images/generators.png)
n this case, Int Range will create keys between the range of 1 to 10. Before you Apply the generated values, you have the option to select between Add, Remove and Confine. They do the following:
- Add will add the values if they dont exist as keys yet
- Remove will remove the given values
- Confine will add the values if they dont exist as keys yet, and remove all keys that are not contained in the list of generated values
As as example, assume you have keys 5 to 15 in your dictionary, and have chosen 1 to 10 in the generator. Given the following options, the resulting keys will be as follows:
- Add will result in keys from 1 to 15, because 1 to 4 will be added
- Remove will result in keys 11 to 15, because 5 to 10 will be removed
- Confine will result in 1 to 10, because 1 to 4 will be added and 11 to 15 removed
## Creating Bulk Edit Operations
Some KeyListGenerators exist for enums and ints. But you might want to add your own custom Key Generators. This is easily done by creating a new class that inherits from `KeyListGenerator` and adding the `KeyListGenerator` Attribute to it. See below for the int generator example:
```csharp
using System;
using System.Collections;
using UnityEngine;
namespace AYellowpaper.SerializedCollections.KeysGenerators
{
[KeyListGenerator("Int Range", typeof(int))]
public class IntRangeGenerator : KeyListGenerator
{
[SerializeField]
private int _startValue = 1;
[SerializeField]
private int _endValue = 10;
public override IEnumerable GetKeys(Type type)
{
int dir = Math.Sign(_endValue - _startValue);
dir = dir == 0 ? 1 : dir;
for (int i = _startValue; i != _endValue; i += dir)
yield return i;
yield return _endValue;
}
}
}
```

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: b7adbf5c96b922b459b77fe23d729801
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 7d3a2360c7e15944cbf26d622f2de997
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show More