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,136 @@
#sd-header {
border-top-width: 1px;
border-right-width: 1px;
border-bottom-width: 1px;
border-left-width: 1px;
border-left-color: rgb(26, 26, 26);
border-right-color: rgb(26, 26, 26);
border-top-color: rgb(26, 26, 26);
border-bottom-color: rgb(26, 26, 26);
border-top-left-radius: 3px;
border-top-right-radius: 3px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
background-color: rgb(51, 51, 51);
-unity-text-align: upper-center;
-unity-font-style: bold;
}
#sd-header > #unity-dragline-anchor {
width: 0;
}
#sd-header > #unity-dragline-anchor > #unity-dragline {
left: -5px;
right: -5px;
width: auto;
}
#sd-header Label {
padding-top: 2px;
padding-right: 2px;
padding-bottom: 2px;
padding-left: 2px;
}
#sd-add-row {
border-top-width: 0;
border-right-width: 1px;
border-bottom-width: 0;
border-left-width: 1px;
border-left-color: rgb(26, 26, 26);
border-right-color: rgb(26, 26, 26);
border-top-color: rgb(26, 26, 26);
border-bottom-color: rgb(26, 26, 26);
background-color: rgb(70, 70, 70);
}
#sd-add-row:disabled {
display: none;
}
.keys-disabled #sd-add-row {
display: none;
}
#sd-add-row > PropertyField {
}
#sd-add-row > Label {
-unity-text-align: middle-center;
}
#sd-list > ScrollView {
border-top-left-radius: 0;
border-top-right-radius: 0;
padding-top: 0;
padding-right: 0;
padding-bottom: 0;
padding-left: 0;
}
#sd-list #unity-list-view__reorderable-handle {
padding-top: 0;
padding-right: 0;
padding-bottom: 0;
padding-left: 0;
align-content: center;
justify-content: center;
align-items: center;
}
#sd-list #unity-list-view__reorderable-item__container {
padding-top: 0;
padding-right: 0;
padding-bottom: 0;
padding-left: 0;
margin-top: 0;
margin-right: 0;
margin-bottom: 0;
margin-left: -15px;
}
.keys-disabled #sd-list #unity-list-view__footer {
display: none;
}
#sd-list #unity-list-view__footer:disabled {
display: none;
}
.row {
flex-direction: row;
flex-grow: 0;
}
.row-left {
margin-top: 0;
margin-right: 0;
margin-bottom: 0;
margin-left: 0;
padding-top: 1px;
padding-right: 2px;
padding-bottom: 1px;
padding-left: 17px;
width: 150px;
overflow: hidden;
border-right-color: rgb(26, 26, 26);
border-right-width: 1px;
}
.row-right {
margin-top: 0;
margin-right: 0;
margin-bottom: 0;
margin-left: 0;
padding-top: 1px;
padding-right: 2px;
padding-bottom: 1px;
padding-left: 2px;
overflow: hidden;
flex-grow: 1;
border-left-width: 1px;
border-left-color: rgb(26, 26, 26);
flex-shrink: 1;
min-width: 180px;
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 215036bf11e09364a9da52f92c9534f0
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
disableValidation: 0
@@ -0,0 +1,14 @@
<ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="True">
<Style src="project://database/Assets/TheVVaS/RPGCoreCommon/Helpers/Editor/Drawers/SerializableDictionary.uss?fileID=7433441132597879392&amp;guid=215036bf11e09364a9da52f92c9534f0&amp;type=3#SerializableDictionary" />
<ui:Foldout text="Label">
<RPGCoreCommon.Helpers.UIElements.TwoPaneSplitView name="sd-header" fixed-pane-initial-dimension="150" class="row">
<ui:Label text="KEY" class="row-left" />
<ui:Label text="VALUE" class="row-right" />
</RPGCoreCommon.Helpers.UIElements.TwoPaneSplitView>
<ui:VisualElement name="sd-add-row" class="row">
<uie:PropertyField class="row-left" />
<ui:Label text="&lt;-- select key before add" class="row-right" />
</ui:VisualElement>
<ui:ListView show-add-remove-footer="true" name="sd-list" reorderable="true" show-alternating-row-backgrounds="All" selection-type="Multiple" show-border="true" virtualization-method="DynamicHeight" reorder-mode="Animated" show-bound-collection-size="false" />
</ui:Foldout>
</ui:UXML>
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: f99f6caeffbe77e4c9b1062ec393fbc8
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
@@ -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));
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 6ea25817e38041b7a6865551cf7cf6f9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- _visualTreeAsset: {fileID: 9197481963319205126, guid: f99f6caeffbe77e4c9b1062ec393fbc8, type: 3}
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using RPGCoreCommon.Helpers.CustomTypes;
using RPGCoreCommon.Helpers.Editor.UIElements;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
namespace RPGCoreCommon.Helpers.Editor.Drawers
{
[CustomPropertyDrawer(typeof(SerializableType))]
public class SerializableTypeDrawer : PropertyDrawer
{
private const string NoTypeSelected = "No Type Selected";
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
var typeField = new FakeObjectField(preferredLabel);
typeField.TrackPropertyValue(property, _ => UpdateTypeField(property, typeField));
typeField.RegisterCallback<ClickEvent>(_ => ShowPopup(property, typeField), TrickleDown.TrickleDown);
UpdateTypeField(property, typeField);
return typeField;
}
private void UpdateTypeField(SerializedProperty property, FakeObjectField field)
{
var typeName = ((SerializableType)property.boxedValue).type?.Name ?? NoTypeSelected;
field.Set(typeName, EditorGUIUtility.FindTexture("cs Script Icon"));
}
private void ShowPopup(SerializedProperty property, FakeObjectField field)
{
new SearchFieldPopup(field, GetOptions(property)).Show();
}
private Dictionary<string, Action> GetOptions(SerializedProperty property)
{
var attr = fieldInfo.GetCustomAttribute<SerializableTypeAttribute>();
var derivedFrom = attr?.derivedFrom ?? typeof(object);
var limitedTo = attr?.limitedTo;
if (limitedTo?.Length > 0)
{
return limitedTo
.GroupBy(type => type.FullName)
.Select(group => group.First())
.ToDictionary(type => type.FullName, type => new Action(() => SelectType(property, type)));
}
return TypeCache.GetTypesDerivedFrom(derivedFrom)
.AppendIf(derivedFrom, attr?.includeSelf ?? false)
.Where(type => type.IsClass && type.IsPublic && !type.IsGenericType)
.Where(type => (attr?.allowAbstract ?? false) || !type.IsAbstract)
.GroupBy(type => type.FullName)
.Select(group => group.First())
.ToDictionary(type => type.FullName, type => new Action(() => SelectType(property, type)));
}
private void SelectType(SerializedProperty property, Type type)
{
property.boxedValue = new SerializableType(type);
property.serializedObject.ApplyModifiedProperties();
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 71afe6e8d45f4770b749f120130fa18a
timeCreated: 1762517198