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,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: