This commit is contained in:
2026-04-25 23:37:10 +02:00
commit 19d6bd934a
476 changed files with 9198 additions and 0 deletions
@@ -0,0 +1,187 @@
using System;
using System.Linq;
using System.Reflection;
using RPGCoreCommon.Helpers.CustomTypes;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
namespace RPGCoreCommon.Helpers.Editor.Drawers
{
[Serializable]
[CustomPropertyDrawer(typeof(SerializableDictionary<,>), true)]
public class SerializableDictionaryDrawer : PropertyDrawer
{
[SerializeField] private VisualTreeAsset _visualTreeAsset;
private SerializedProperty _property;
private SerializedProperty _tempKeyProperty;
private SerializedProperty _keysProperty;
private SerializedProperty _valuesProperty;
private SerializableDictionaryAttribute _sdAttribute;
private Type _keyType;
private Type _valueType;
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
_property = property;
_sdAttribute = fieldInfo.GetCustomAttribute<SerializableDictionaryAttribute>() ?? new SerializableDictionaryAttribute();
_tempKeyProperty = property.FindPropertyRelative("_tempKey");
_keysProperty = property.FindPropertyRelative("_keys");
_valuesProperty = property.FindPropertyRelative("_values");
if (_keysProperty == null || _valuesProperty == null)
return new HelpBox("Keys and Values in SerializableDictionary must be serializable!", HelpBoxMessageType.Error);
var genericTypes = property.boxedValue.GetType().FindGenericDefinitionType(typeof(SerializableDictionary<,>)).GetGenericArguments();
_keyType = genericTypes[0];
_valueType = genericTypes[1];
var root = _visualTreeAsset.CloneTree();
root.Q<Label>().text = preferredLabel;
root.DisableDefaultAligning();
if (string.IsNullOrEmpty(preferredLabel))
{
var foldout = root.Q<Foldout>();
foldout.Q<Toggle>().style.display = DisplayStyle.None;
foldout.Q<Toggle>().value = true;
foldout.Q(name: "unity-content").style.marginLeft = 0;
}
var list = root.Q<ListView>("sd-list");
list.BindProperty(_valuesProperty);
list.makeItem = CreateRow;
list.bindItem = BindRow;
list.onAdd = OnAdd;
list.onRemove = OnRemove;
list.itemIndexChanged += OnSwap;
if (_sdAttribute is not null)
{
root.Q("sd-header").Q<Label>(className: "row-left").text = _sdAttribute.keyName;
root.Q("sd-header").Q<Label>(className: "row-right").text = _sdAttribute.valueName;
root.EnableInClassList("keys-disabled", !_sdAttribute.isKeyEditable);
}
var dragline = root.Q("sd-header").Q("unity-dragline-anchor");
dragline.RegisterCallback<GeometryChangedEvent>(ev =>
{
var parentWidth = dragline.parent.worldBound.width;
var leftWidth = dragline.style.left.value.value;
leftWidth = Mathf.Clamp(leftWidth, 150f, parentWidth-180f);
dragline.style.left = leftWidth;
root.Query(className: "row-left").ForEach(element => element.style.width = leftWidth);
});
var tempKeyField = root.Q("sd-add-row").Q<PropertyField>();
tempKeyField.BindProperty(_tempKeyProperty);
tempKeyField.HideFoldoutAndLabel();
// This additional key property should be always empty on beginning to not confuse user.
// All because we are using "fake" serialized property to make unity draw field for key before even adding it to dictionary.
_tempKeyProperty.boxedValue = _keyType.IsValueType ? Activator.CreateInstance(_keyType) : null;
_property.serializedObject.ApplyModifiedProperties();
_property.serializedObject.Update();
return root;
}
private void OnSwap(int index1, int index2)
{
_keysProperty.MoveArrayElement(index1, index2);
_property.serializedObject.ApplyModifiedProperties();
}
private void OnAdd(BaseListView list)
{
var tempKey = _tempKeyProperty.boxedValue;
if (tempKey == null)
{
var helpBox = new HelpBox("Key is not selected!", HelpBoxMessageType.Error);
helpBox.FadeOut(3000, 2000, true);
list.parent.parent.Insert(0, helpBox);
return;
}
var keyExists = Enumerable.Range(0, _keysProperty.arraySize)
.Select(i => _keysProperty.GetArrayElementAtIndex(i))
.Any(prop => prop.boxedValue.Equals(tempKey));
if (keyExists)
{
var helpBox = new HelpBox("Key already exists!", HelpBoxMessageType.Error);
helpBox.FadeOut(3000, 2000, true);
list.parent.parent.Insert(0, helpBox);
return;
}
// Reset temporary key to default
_tempKeyProperty.boxedValue = _keyType.IsValueType ? Activator.CreateInstance(_keyType) : null;
// New Key
_keysProperty.arraySize++;
_keysProperty.GetArrayElementAtIndex(_keysProperty.arraySize - 1).boxedValue = tempKey;
// New Value
_valuesProperty.arraySize++;
_valuesProperty.GetArrayElementAtIndex(_valuesProperty.arraySize - 1).boxedValue =
_valueType.IsValueType ? Activator.CreateInstance(_valueType) : null;
// Update list and property
_property.serializedObject.ApplyModifiedProperties();
_property.serializedObject.Update();
list.RefreshItems();
}
private void OnRemove(BaseListView list)
{
var ids = list.selectedIds.SortDescending().ToList();
if (ids.Count < 0) return;
ids.ForEach(i =>
{
_keysProperty.DeleteArrayElementAtIndex(i);
_valuesProperty.DeleteArrayElementAtIndex(i);
});
// Update list and property
_property.serializedObject.ApplyModifiedProperties();
_property.serializedObject.Update();
list.RefreshItems();
}
private VisualElement CreateRow()
{
var row = new VisualElement();
row.AddToClassList("row");
var keyField = new PropertyField();
keyField.DisableDefaultAligning();
keyField.AddToClassList("row-left");
keyField.label = "";
keyField.SetEnabled(_sdAttribute.isKeyEditable);
row.Add(keyField);
var valueField = new PropertyField();
valueField.HideFoldoutAndLabel();
valueField.SetContextAligningHere();
valueField.AddToClassList("row-right");
valueField.label = "";
row.Add(valueField);
return row;
}
private void BindRow(VisualElement row, int i)
{
if (i >= _keysProperty.arraySize) return;
row.Q<PropertyField>(className: "row-left").BindProperty(_keysProperty.GetArrayElementAtIndex(i));
row.Q<PropertyField>(className: "row-right").BindProperty(_valuesProperty.GetArrayElementAtIndex(i));
}
}
}