init
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bcb9b22dd65d7324f920cbdbad57fb0a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5de987142cf530f4dbc778193b3cb9ab
|
||||
timeCreated: 1776005920
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "RPGCoreCommon.Debugger.Editor",
|
||||
"rootNamespace": "RPGCoreCommon.Debugger",
|
||||
"references": [
|
||||
"RPGCoreCommon.Debugger"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 527334016fe26c649a09686dc0a7ba5d
|
||||
timeCreated: 1776005962
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5be5cfce8c2e47449c59b6d3ac62a1d
|
||||
timeCreated: 1776005926
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "RPGCoreCommon.Debugger",
|
||||
"rootNamespace": "RPGCoreCommon.Debugger",
|
||||
"references": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0567806f31a0634458422c1f723e1a49
|
||||
timeCreated: 1776005940
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 56524ae74f554e0d9d7470f4cf9c338d
|
||||
timeCreated: 1760521408
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4914a4d27525472aac1393e98ed6b8e2
|
||||
timeCreated: 1760521419
|
||||
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RPGCoreCommon.Helpers;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace RPGCoreCommon.DropTable.Editor
|
||||
{
|
||||
[Serializable]
|
||||
[CanEditMultipleObjects]
|
||||
[CustomEditor(typeof(DropTableSO))]
|
||||
public class DropTableDrawer : UnityEditor.Editor
|
||||
{
|
||||
[SerializeField] private VisualTreeAsset _groupVisualTreeAsset;
|
||||
[SerializeField] private VisualTreeAsset _rowVisualTreeAsset;
|
||||
public static VisualTreeAsset groupVisualTreeAsset { get; private set; }
|
||||
public static VisualTreeAsset rowVisualTreeAsset { get; private set; }
|
||||
|
||||
public override VisualElement CreateInspectorGUI()
|
||||
{
|
||||
groupVisualTreeAsset = _groupVisualTreeAsset;
|
||||
rowVisualTreeAsset = _rowVisualTreeAsset;
|
||||
|
||||
if (serializedObject.isEditingMultipleObjects)
|
||||
return new HelpBox($"Multiple {nameof(DropTableSO)} editing is disabled.", HelpBoxMessageType.Warning);
|
||||
|
||||
var property = serializedObject.FindProperty(nameof(DropTableSO.group));
|
||||
|
||||
var wrapper = new VisualElement();
|
||||
wrapper.schedule.Execute(() =>
|
||||
{
|
||||
var mainInspector = wrapper.GetFirstAncestorOfType<InspectorElement>();
|
||||
mainInspector.style.paddingLeft = 4f;
|
||||
mainInspector.style.paddingRight = 4f;
|
||||
});
|
||||
|
||||
var resultBox = new HelpBox();
|
||||
resultBox.style.display = DisplayStyle.None;
|
||||
resultBox.style.unityTextAlign = TextAnchor.MiddleCenter;
|
||||
|
||||
var resultButton = new Button();
|
||||
resultButton.style.marginTop = 20f;
|
||||
resultButton.clicked += () => PrintResult(resultBox, property.boxedValue as DropTableGroup);
|
||||
resultButton.text = "Try your luck...";
|
||||
|
||||
wrapper.Add(new DropTableGroupElement(property));
|
||||
wrapper.Add(resultButton);
|
||||
wrapper.Add(resultBox);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
private void PrintResult(HelpBox resultBox, DropTableGroup value)
|
||||
{
|
||||
resultBox.Clear();
|
||||
resultBox.style.display = DisplayStyle.Flex;
|
||||
var result = new Dictionary<Object, int>();
|
||||
value.Get(ref result);
|
||||
result.DictForEach((item, count) =>
|
||||
{
|
||||
var row = new VisualElement();
|
||||
row.style.flexDirection = FlexDirection.Row;
|
||||
row.Add(new ObjectField{value = item, enabledSelf = false});
|
||||
row.Add(new Label($" -> {count}"));
|
||||
resultBox.Add(row);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 287610d3321e42ccb5dbad00b9b585e5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences:
|
||||
- _groupVisualTreeAsset: {fileID: 9197481963319205126, guid: 6d2d365fc9f0e0e4a93cee141a824492, type: 3}
|
||||
- _rowVisualTreeAsset: {fileID: 9197481963319205126, guid: 0b23d9b3ec1017740933e19785b8964b, type: 3}
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,85 @@
|
||||
#dt-config {
|
||||
background-color: rgba(0, 0, 0, 0.39);
|
||||
border-top-width: 0;
|
||||
border-right-width: 0;
|
||||
border-bottom-width: 0;
|
||||
border-left-width: 0;
|
||||
}
|
||||
|
||||
#dt-config > * {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
-unity-text-align: middle-center;
|
||||
margin-left: 1px;
|
||||
margin-right: 1px;
|
||||
}
|
||||
|
||||
#dt-config > ToolbarSpacer {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
#dt-config > #dt-add-row {
|
||||
border-top-width: 1px;
|
||||
border-right-width: 1px;
|
||||
border-bottom-width: 1px;
|
||||
border-left-width: 1px;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
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);
|
||||
-unity-text-align: middle-center;
|
||||
font-size: 14px;
|
||||
-unity-font-style: bold;
|
||||
}
|
||||
|
||||
#dt-config > #dt-chance-type {
|
||||
}
|
||||
|
||||
#dt-config > #dt-chance-type > VisualElement {
|
||||
margin-top: 0;
|
||||
margin-right: 0;
|
||||
margin-bottom: 0;
|
||||
margin-left: 0;
|
||||
padding-top: 0;
|
||||
padding-right: 0;
|
||||
padding-bottom: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
#dt-config > #dt-chance-type > VisualElement > TextElement {
|
||||
margin-top: 0;
|
||||
margin-right: 0;
|
||||
margin-bottom: 0;
|
||||
margin-left: 0;
|
||||
padding-right: 8px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
#dt-list > ScrollView {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
#unity-list-view__reorderable-item__container {
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
padding-bottom: 1px;
|
||||
padding-top: 1px;
|
||||
}
|
||||
|
||||
#unity-list-view__reorderable-handle {
|
||||
justify-content: center;
|
||||
margin-top: 0;
|
||||
margin-right: 0;
|
||||
margin-bottom: 0;
|
||||
margin-left: 0;
|
||||
padding-top: 0;
|
||||
padding-right: 0;
|
||||
padding-bottom: 0;
|
||||
padding-left: 0;
|
||||
display: none;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f1b1bb265dde2264f9cb1caf449494f3
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||
disableValidation: 0
|
||||
@@ -0,0 +1,11 @@
|
||||
<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/DropTable/Editor/DropTableGroup.uss?fileID=7433441132597879392&guid=f1b1bb265dde2264f9cb1caf449494f3&type=3#DropTableGroup" />
|
||||
<ui:VisualElement name="dt-group">
|
||||
<uie:Toolbar name="dt-config">
|
||||
<ui:EnumField value="Center" type="RPGCoreCommon.DropTable.ChanceTypeEnum, RPGCoreCommon.DropTable" name="dt-chance-type" />
|
||||
<uie:ToolbarSpacer />
|
||||
<ui:Button text="+" name="dt-add-row" />
|
||||
</uie:Toolbar>
|
||||
<ui:ListView name="dt-list" reorder-mode="Animated" reorderable="true" selection-type="None" virtualization-method="DynamicHeight" show-border="true" show-bound-collection-size="false" />
|
||||
</ui:VisualElement>
|
||||
</ui:UXML>
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6d2d365fc9f0e0e4a93cee141a824492
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
|
||||
@@ -0,0 +1,80 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace RPGCoreCommon.DropTable.Editor
|
||||
{
|
||||
public class DropTableGroupElement : VisualElement
|
||||
{
|
||||
// Property data
|
||||
private SerializedProperty _property;
|
||||
private SerializedProperty _propertyRows;
|
||||
private List<DropTableGroupRowElement> _rowElements = new();
|
||||
|
||||
// Elements
|
||||
private readonly EnumField _chanceTypeField;
|
||||
private readonly VisualElement _addRowButton;
|
||||
private readonly ListView _list;
|
||||
|
||||
public DropTableGroupElement(SerializedProperty property) : this()
|
||||
{
|
||||
BindProperty(property);
|
||||
}
|
||||
|
||||
public DropTableGroupElement()
|
||||
{
|
||||
DropTableDrawer.groupVisualTreeAsset.CloneTree(this);
|
||||
|
||||
SetEnabled(false);
|
||||
|
||||
// CHANCE TYPE
|
||||
_chanceTypeField = this.Q<EnumField>("dt-chance-type");
|
||||
_chanceTypeField.RegisterValueChangedCallback(_ => Refresh());
|
||||
|
||||
// ADD (replacement for default list button)
|
||||
_addRowButton = this.Q("dt-add-row");
|
||||
_addRowButton.RegisterCallback<ClickEvent>(_ => _list.onAdd(_list));
|
||||
|
||||
// LIST
|
||||
_list = this.Q<ListView>("dt-list");
|
||||
_list.makeItem = () => new DropTableGroupRowElement();
|
||||
_list.bindItem = (element, i) =>
|
||||
{
|
||||
var rowElement = element as DropTableGroupRowElement;
|
||||
_rowElements.Add(rowElement);
|
||||
rowElement.BindProperty(_propertyRows.GetArrayElementAtIndex(i));
|
||||
};
|
||||
_list.unbindItem = (element, _) => _rowElements.Remove(element as DropTableGroupRowElement);
|
||||
_list.onAdd = _ =>
|
||||
{
|
||||
_propertyRows.arraySize++;
|
||||
_propertyRows.GetArrayElementAtIndex(_propertyRows.arraySize - 1).boxedValue = new DropTableGroupRow();
|
||||
_propertyRows.serializedObject.ApplyModifiedProperties();
|
||||
_list.RefreshItems();
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Created by <see cref="DropTableDrawer"/>. First in hierarchy <see cref="DropTableGroupElement"/>.
|
||||
/// </summary>
|
||||
internal void BindProperty(SerializedProperty property)
|
||||
{
|
||||
_property = property;
|
||||
_propertyRows = _property.FindPropertyRelative(nameof(DropTableGroup.rows));
|
||||
|
||||
_chanceTypeField.BindProperty(_property.FindPropertyRelative(nameof(DropTableGroup.chanceType)));
|
||||
_list.BindProperty(_propertyRows);
|
||||
|
||||
SetEnabled(true);
|
||||
}
|
||||
|
||||
internal void Refresh()
|
||||
{
|
||||
var weightSum = (_property.boxedValue as DropTableGroup).rows.Sum(row => row.chance);
|
||||
_rowElements.ForEach(row => row.Refresh((ChanceTypeEnum)_chanceTypeField.value, weightSum));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b971c281bc214ba8b7502d1f1a22cbd1
|
||||
timeCreated: 1760895922
|
||||
@@ -0,0 +1,130 @@
|
||||
#dt-row {
|
||||
flex-direction: row;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
|
||||
#dt-percent-background {
|
||||
background-color: rgba(255, 0, 0, 0.29);
|
||||
align-self: stretch;
|
||||
justify-content: center;
|
||||
cursor: resize-vertical;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
|
||||
#dt-percent {
|
||||
color: rgb(255, 250, 250);
|
||||
-unity-font-style: bold;
|
||||
-unity-text-align: middle-center;
|
||||
margin-top: 0;
|
||||
margin-right: -5px;
|
||||
margin-bottom: 0;
|
||||
margin-left: -5px;
|
||||
padding-top: 0;
|
||||
padding-right: 0;
|
||||
padding-bottom: 0;
|
||||
padding-left: 0;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
#dt-right {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
#dt-right.single-line {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
#dt-header {
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
#dt-header > * {
|
||||
margin-top: 1px;
|
||||
margin-right: 1px;
|
||||
margin-bottom: 1px;
|
||||
margin-left: 1px;
|
||||
}
|
||||
|
||||
#dt-header > #dt-chance {
|
||||
}
|
||||
|
||||
#dt-header > #dt-chance > Label {
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
#dt-header > #dt-chance > #unity-text-input {
|
||||
flex-grow: 0;
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
#dt-header > #dt-count {
|
||||
}
|
||||
|
||||
#dt-header > #dt-count > Label {
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
#dt-header > #dt-count > #unity-text-input {
|
||||
flex-grow: 0;
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
#dt-body {
|
||||
min-height: 20px;
|
||||
flex-grow: 1;
|
||||
padding-left: 3px;
|
||||
}
|
||||
|
||||
#dt-body > ObjectField {
|
||||
margin-top: 0;
|
||||
margin-right: 0;
|
||||
margin-bottom: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
#dt-body > ObjectField > Label {
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
#dt-drop-here {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--unity-colors-window-background);
|
||||
border-top-width: 1px;
|
||||
border-right-width: 1px;
|
||||
border-bottom-width: 1px;
|
||||
border-left-width: 1px;
|
||||
border-left-color: rgb(0, 0, 0);
|
||||
border-right-color: rgb(0, 0, 0);
|
||||
border-top-color: rgb(0, 0, 0);
|
||||
border-bottom-color: rgb(0, 0, 0);
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-image: url("project://database/Packages/com.unity.timeline/Editor/StyleSheets/Images/DarkSkin/TimelineDisabledBackground.png?fileID=2800000&guid=412f73153f3a3c549b67b2a847741145&type=3#TimelineDisabledBackground");
|
||||
display: none;
|
||||
}
|
||||
|
||||
#dt-drop-here:hover {
|
||||
-unity-background-image-tint-color: rgb(255, 235, 4);
|
||||
border-left-color: rgb(126, 117, 2);
|
||||
border-right-color: rgb(126, 117, 2);
|
||||
border-top-color: rgb(126, 117, 2);
|
||||
border-bottom-color: rgb(126, 117, 2);
|
||||
}
|
||||
|
||||
#dt-drop-here > #dt-new-inline {
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a4b26fa3a17d7914bb564222d2d3c1b4
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||
disableValidation: 0
|
||||
@@ -0,0 +1,22 @@
|
||||
<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/DropTable/Editor/DropTableGroupRow.uss?fileID=7433441132597879392&guid=a4b26fa3a17d7914bb564222d2d3c1b4&type=3#DropTableGroupRow" />
|
||||
<ui:VisualElement name="dt-row">
|
||||
<ui:VisualElement name="dt-percent-background">
|
||||
<ui:Label text="12.34%" name="dt-percent" picking-mode="Ignore" />
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement name="dt-right">
|
||||
<ui:VisualElement name="dt-header">
|
||||
<ui:FloatField value="42.2" name="dt-chance" label="{chance}:" />
|
||||
<ui:IntegerField value="42" name="dt-count" label="Count:" />
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement name="dt-body">
|
||||
<uie:ObjectField name="dt-item" allow-scene-objects="false" label="Item:" />
|
||||
<uie:ObjectField name="dt-group-reference" allow-scene-objects="false" />
|
||||
</ui:VisualElement>
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement name="dt-drop-here" class="drop-area" style="display: none;">
|
||||
<ui:Label text="Drop <b>Asset</b> or <b>DropTableSO</b> here<br>" />
|
||||
<ui:Label text="Or <u>Click Here</u> to create inline" name="dt-new-inline" />
|
||||
</ui:VisualElement>
|
||||
</ui:VisualElement>
|
||||
</ui:UXML>
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0b23d9b3ec1017740933e19785b8964b
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
|
||||
@@ -0,0 +1,263 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using Cursor = UnityEngine.Cursor;
|
||||
|
||||
namespace RPGCoreCommon.DropTable.Editor
|
||||
{
|
||||
internal class DropTableGroupRowElement : VisualElement
|
||||
{
|
||||
// Property data
|
||||
private SerializedProperty _property;
|
||||
private SerializedProperty _propertyInlineGroup;
|
||||
private DropTableGroupRow value => (DropTableGroupRow)_property.boxedValue;
|
||||
|
||||
// Elements
|
||||
private readonly VisualElement _percentBackground;
|
||||
private readonly Label _percentLabel;
|
||||
private readonly VisualElement _rightElement;
|
||||
private readonly FloatField _chanceField;
|
||||
private readonly IntegerField _countField;
|
||||
private readonly ObjectField _itemField;
|
||||
private readonly ObjectField _groupReferenceField;
|
||||
private readonly DropTableGroupElement _groupInline;
|
||||
private readonly Label _newInlineLabel;
|
||||
private readonly VisualElement _dropHere;
|
||||
|
||||
internal DropTableGroupRowElement()
|
||||
{
|
||||
DropTableDrawer.rowVisualTreeAsset.CloneTree(this);
|
||||
|
||||
this.AddManipulator(new ContextualMenuManipulator(null));
|
||||
RegisterCallback<ContextualMenuPopulateEvent>(ev =>
|
||||
{
|
||||
ev.StopImmediatePropagation();
|
||||
|
||||
ev.menu.InsertAction(0, "Reset row", _ =>
|
||||
{
|
||||
SetEditingMode(EditTypeEnum.None);
|
||||
});
|
||||
ev.menu.InsertAction(1, "Remove row", _ =>
|
||||
{
|
||||
var arrayPath = string.Join(".", _property.propertyPath.Split(".").SkipLast(2));
|
||||
var arrayProperty = _property.serializedObject.FindProperty(arrayPath);
|
||||
var i = int.Parse(_property.propertyPath[_property.propertyPath.LastIndexOf('[')..].Trim('[', ']'));
|
||||
arrayProperty.DeleteArrayElementAtIndex(i);
|
||||
_property.serializedObject.ApplyModifiedProperties();
|
||||
});
|
||||
ev.menu.InsertSeparator(null, 2);
|
||||
});
|
||||
|
||||
// PERCENTAGE
|
||||
_percentBackground = this.Q("dt-percent-background");
|
||||
_percentLabel = this.Q<Label>("dt-percent");
|
||||
|
||||
// RIGHT SIDE
|
||||
_rightElement = this.Q("dt-right");
|
||||
|
||||
// CHANCE
|
||||
_chanceField = this.Q<FloatField>("dt-chance");
|
||||
_chanceField.RegisterValueChangedCallback(_ =>
|
||||
{
|
||||
_chanceField.SetValueWithoutNotify(Mathf.Clamp(_chanceField.value, 0, 100));
|
||||
RefreshGroup();
|
||||
});
|
||||
|
||||
// COUNT
|
||||
_countField = this.Q<IntegerField>("dt-count");
|
||||
_countField.RegisterValueChangedCallback(_ => _countField.SetValueWithoutNotify(Mathf.Max(0, _countField.value)));
|
||||
|
||||
// CREATE INLINE
|
||||
_newInlineLabel = this.Q<Label>("dt-new-inline");
|
||||
_newInlineLabel.RegisterCallback<ClickEvent>(_ => SetEditingMode(EditTypeEnum.InlineGroup));
|
||||
|
||||
// DROP AREA
|
||||
_dropHere = this.Q("dt-drop-here");
|
||||
RegisterCallback<DragLeaveEvent>(_ => ShowDropHere());
|
||||
RegisterCallback<DragExitedEvent>(_ => ShowDropHere());
|
||||
RegisterCallback<DragUpdatedEvent>(ev =>
|
||||
{
|
||||
if ((ev.target as VisualElement).GetFirstAncestorOfType<DropTableGroupRowElement>() != this)
|
||||
{
|
||||
ShowDropHere(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.subGroupInline is not null)
|
||||
{
|
||||
ShowDropHere(false);
|
||||
return;
|
||||
}
|
||||
|
||||
ShowDropHere(true);
|
||||
});
|
||||
RegisterCallback<DragPerformEvent>(ev =>
|
||||
{
|
||||
ev.StopImmediatePropagation();
|
||||
|
||||
if (DragAndDrop.objectReferences.Length > 1)
|
||||
Debug.LogWarning("Trying to drag more than one <b>Object</b> to row, using only first one.");
|
||||
|
||||
SetEditingMode(EditTypeEnum.None);
|
||||
|
||||
var obj = DragAndDrop.objectReferences[0];
|
||||
if (obj is DropTableSO dropTableSO)
|
||||
{
|
||||
_groupReferenceField.value = dropTableSO;
|
||||
SetEditingMode(EditTypeEnum.ReferenceGroup);
|
||||
}
|
||||
else
|
||||
{
|
||||
_itemField.value = obj;
|
||||
SetEditingMode(EditTypeEnum.Item);
|
||||
}
|
||||
|
||||
ShowDropHere();
|
||||
});
|
||||
|
||||
// ITEM
|
||||
_itemField = this.Q<ObjectField>("dt-item");
|
||||
|
||||
// SUBGROUP REF
|
||||
_groupReferenceField = this.Q<ObjectField>("dt-group-reference");
|
||||
_groupReferenceField.objectType = typeof(DropTableSO);
|
||||
|
||||
// SUBGROUP INLINE
|
||||
_groupInline = new DropTableGroupElement { name = "dt-group-inline" };
|
||||
this.Q("dt-body").Add(_groupInline);
|
||||
|
||||
SetEnabled(false);
|
||||
}
|
||||
|
||||
private void ShowDropHere()
|
||||
{
|
||||
ShowDropHere(!value.item && !value.subGroupRef && value.subGroupInline is null);
|
||||
}
|
||||
|
||||
private void ShowDropHere(bool show)
|
||||
{
|
||||
if (show)
|
||||
{
|
||||
_dropHere.style.display = DisplayStyle.Flex;
|
||||
_dropHere.pickingMode = PickingMode.Position;
|
||||
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
|
||||
|
||||
ShowCreateNewInline(!value.item && !value.subGroupRef && value.subGroupInline is null);
|
||||
}
|
||||
else
|
||||
{
|
||||
_dropHere.style.display = DisplayStyle.None;
|
||||
_dropHere.pickingMode = PickingMode.Ignore;
|
||||
ShowCreateNewInline(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowCreateNewInline(bool show)
|
||||
{
|
||||
_newInlineLabel.style.display = show ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
}
|
||||
|
||||
internal void BindProperty(SerializedProperty property)
|
||||
{
|
||||
SetEnabled(true);
|
||||
|
||||
_property = property;
|
||||
_propertyInlineGroup = property.FindPropertyRelative(nameof(DropTableGroupRow.subGroupInline));
|
||||
var value = _property.boxedValue as DropTableGroupRow;
|
||||
|
||||
_chanceField.BindProperty(_property.FindPropertyRelative(nameof(DropTableGroupRow.chance)));
|
||||
_countField.BindProperty(_property.FindPropertyRelative(nameof(DropTableGroupRow.count)));
|
||||
_itemField.BindProperty(_property.FindPropertyRelative(nameof(DropTableGroupRow.item)));
|
||||
_groupReferenceField.BindProperty(_property.FindPropertyRelative(nameof(DropTableGroupRow.subGroupRef)));
|
||||
|
||||
// Default editing mode by currently set content
|
||||
if (value.item) SetEditingMode(EditTypeEnum.Item);
|
||||
else if (value.subGroupRef) SetEditingMode(EditTypeEnum.ReferenceGroup);
|
||||
else if (value.subGroupInline != null) SetEditingMode(EditTypeEnum.InlineGroup);
|
||||
else SetEditingMode(EditTypeEnum.None);
|
||||
}
|
||||
|
||||
private void SetEditingMode(EditTypeEnum @enum)
|
||||
{
|
||||
// Hide all
|
||||
_itemField.style.display = DisplayStyle.None;
|
||||
_groupReferenceField.style.display = DisplayStyle.None;
|
||||
_groupInline.style.display = DisplayStyle.None;
|
||||
|
||||
switch (@enum)
|
||||
{
|
||||
// 1. Starting point when row is newly created with no values
|
||||
// 2. When row is resetting - clean values
|
||||
case EditTypeEnum.None:
|
||||
_itemField.value = null;
|
||||
_groupReferenceField.value = null;
|
||||
_propertyInlineGroup.managedReferenceValue = null;
|
||||
_rightElement.EnableInClassList("single-line", false);
|
||||
ShowDropHere(true);
|
||||
break;
|
||||
// Editing selected - item
|
||||
case EditTypeEnum.Item:
|
||||
_itemField.style.display = DisplayStyle.Flex;
|
||||
_rightElement.EnableInClassList("single-line", true);
|
||||
ShowDropHere(false);
|
||||
break;
|
||||
// Editing selected - recursion by reference (ScriptableObject)
|
||||
case EditTypeEnum.ReferenceGroup:
|
||||
_groupReferenceField.style.display = DisplayStyle.Flex;
|
||||
_rightElement.EnableInClassList("single-line", true);
|
||||
ShowDropHere(false);
|
||||
break;
|
||||
// Editing selected - recursion by inline definition
|
||||
case EditTypeEnum.InlineGroup:
|
||||
_groupInline.style.display = DisplayStyle.Flex;
|
||||
_propertyInlineGroup.managedReferenceValue ??= new DropTableGroup();
|
||||
_property.serializedObject.ApplyModifiedProperties();
|
||||
_groupInline.BindProperty(_propertyInlineGroup);
|
||||
_rightElement.EnableInClassList("single-line", false);
|
||||
ShowDropHere(false);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
internal void RefreshGroup()
|
||||
{
|
||||
GetFirstAncestorOfType<DropTableGroupElement>().Refresh();
|
||||
}
|
||||
|
||||
internal void Refresh(ChanceTypeEnum chanceType, float weightSum)
|
||||
{
|
||||
switch (chanceType)
|
||||
{
|
||||
case ChanceTypeEnum.All:
|
||||
_chanceField.style.display = DisplayStyle.None;
|
||||
_percentBackground.style.display = DisplayStyle.None;
|
||||
break;
|
||||
case ChanceTypeEnum.ByWeight:
|
||||
_chanceField.style.display = DisplayStyle.Flex;
|
||||
_chanceField.label = "Weight:";
|
||||
_percentBackground.style.display = DisplayStyle.Flex;
|
||||
SetPercent(_chanceField.value/weightSum*100);
|
||||
break;
|
||||
case ChanceTypeEnum.ByPercent:
|
||||
_chanceField.style.display = DisplayStyle.Flex;
|
||||
_chanceField.label = "Percent:";
|
||||
_percentBackground.style.display = DisplayStyle.Flex;
|
||||
SetPercent(_chanceField.value);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(chanceType), chanceType, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetPercent(float percent)
|
||||
{
|
||||
_percentBackground.style.backgroundColor = Color.Lerp(Color.red, Color.green, percent / 100);
|
||||
_percentLabel.text = $"{percent:F}%";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8972582db2c34fdbb953598e5ab5f008
|
||||
timeCreated: 1760896354
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace RPGCoreCommon.DropTable.Editor
|
||||
{
|
||||
internal enum EditTypeEnum
|
||||
{
|
||||
None,
|
||||
Item,
|
||||
ReferenceGroup,
|
||||
InlineGroup
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 15cc33125741460a8632dc8ddecd8f4a
|
||||
timeCreated: 1760810491
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "RPGCoreCommon.DropTable.Editor",
|
||||
"rootNamespace": "RPGCoreCommon.DropTable.Editor",
|
||||
"references": [
|
||||
"RPGCoreCommon.DropTable",
|
||||
"RPGCoreCommon.Helpers"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cbe301a2824a4590a84eaeeb1d960654
|
||||
timeCreated: 1760521419
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b134d2bbe2c24303b5dc46a0bce52ec1
|
||||
timeCreated: 1760521419
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace RPGCoreCommon.DropTable
|
||||
{
|
||||
public enum ChanceTypeEnum
|
||||
{
|
||||
All,
|
||||
ByWeight,
|
||||
ByPercent
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7c0aa7d4711440a697970eb700168030
|
||||
timeCreated: 1760523882
|
||||
@@ -0,0 +1,9 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace RPGCoreCommon.DropTable
|
||||
{
|
||||
public class DropTableComponent : MonoBehaviour
|
||||
{
|
||||
[SerializeReference] private DropTableSO dropTableGroup;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dc0580faa40e4f62bc65a675c74c4c96
|
||||
timeCreated: 1760523736
|
||||
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using RPGCoreCommon.Helpers;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace RPGCoreCommon.DropTable
|
||||
{
|
||||
[Serializable]
|
||||
internal class DropTableGroup
|
||||
{
|
||||
// Rules for randomizing
|
||||
[SerializeField] internal ChanceTypeEnum chanceType = ChanceTypeEnum.ByPercent;
|
||||
|
||||
// SubGroups and/or items of this group
|
||||
[SerializeField] internal List<DropTableGroupRow> rows = new();
|
||||
|
||||
internal void Get(ref Dictionary<Object, int> result)
|
||||
{
|
||||
if (!rows.Any())
|
||||
{
|
||||
Debug.LogWarning($"Empty <b>{nameof(DropTableGroup)}</b>!");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var row in GetRows())
|
||||
{
|
||||
if (row.item)
|
||||
{
|
||||
result.TryAdd(row.item, 0);
|
||||
result[row.item] += row.count;
|
||||
}
|
||||
else if (row.subGroupRef)
|
||||
{
|
||||
for (var i = 0; i < row.count; i++)
|
||||
row.subGroupRef.group.Get(ref result);
|
||||
}
|
||||
else if (row.subGroupInline != null)
|
||||
{
|
||||
for (var i = 0; i < row.count; i++)
|
||||
row.subGroupInline.Get(ref result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal List<DropTableGroupRow> GetRows()
|
||||
{
|
||||
return chanceType switch
|
||||
{
|
||||
ChanceTypeEnum.All => rows,
|
||||
ChanceTypeEnum.ByWeight => RandomHelper.RandomElementsByWeight(rows, row => row.chance, 1),
|
||||
ChanceTypeEnum.ByPercent => rows.Where(row => RandomHelper.Chance(row.chance/100)).ToList(),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e24e6877320d4170b09ca966a136951a
|
||||
timeCreated: 1760709210
|
||||
@@ -0,0 +1,14 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace RPGCoreCommon.DropTable
|
||||
{
|
||||
[System.Serializable]
|
||||
public class DropTableGroupRow
|
||||
{
|
||||
[SerializeField] internal float chance;
|
||||
[SerializeField] internal int count = 1;
|
||||
[SerializeReference] internal DropTableGroup subGroupInline;
|
||||
[SerializeReference] internal DropTableSO subGroupRef;
|
||||
[SerializeReference] internal Object item;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3597a5bce3db4068885d19da0a9e4a2a
|
||||
timeCreated: 1760694296
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RPGCoreCommon.DropTable
|
||||
{
|
||||
[System.Serializable]
|
||||
[CreateAssetMenu(menuName = "RPG Core/Drop Table")]
|
||||
public class DropTableSO : ScriptableObject
|
||||
{
|
||||
[SerializeField] internal DropTableGroup group;
|
||||
|
||||
public Dictionary<Object, int> Get()
|
||||
{
|
||||
var result = new Dictionary<Object, int>();
|
||||
group.Get(ref result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c1984d6153504b90a4dd21a18d5f034a
|
||||
timeCreated: 1760522473
|
||||
@@ -0,0 +1,8 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("RPGCoreCommon.DropTable.Editor")]
|
||||
|
||||
namespace RPGCoreCommon.DropTable
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2efd7efdd7b349f1a73c5a2a4997e9d3
|
||||
timeCreated: 1760693427
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "RPGCoreCommon.DropTable",
|
||||
"rootNamespace": "RPGCoreCommon.DropTable",
|
||||
"references": [
|
||||
"RPGCoreCommon.Helpers"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8788c3c029994136b7e8bbbff4b283f5
|
||||
timeCreated: 1760521419
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 824d97cdc45047cdac972ee4ca0cefaa
|
||||
timeCreated: 1759333990
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c535423420394136821e97a0798f170b
|
||||
timeCreated: 1759333996
|
||||
@@ -0,0 +1,272 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using RPGCoreCommon.DynamicValues;
|
||||
using RPGCoreCommon.Helpers;
|
||||
using RPGCoreCommon.Helpers.CustomTypes;
|
||||
using RPGCoreCommon.Helpers.Exceptions;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace RPGCoreCommon.DynamicValues.Editor
|
||||
{
|
||||
[Serializable]
|
||||
[CustomPropertyDrawer(typeof(DynamicValue))]
|
||||
public class DynamicValueDrawer : PropertyDrawer
|
||||
{
|
||||
[SerializeField] private VisualTreeAsset _visualTreeAsset;
|
||||
|
||||
private SerializedProperty _property;
|
||||
private SerializedProperty _propertySourceDict;
|
||||
private SerializedProperty _propertyTypeDict;
|
||||
private SerializedProperty _propertyFormula;
|
||||
private DynamicValueAttribute _attribute;
|
||||
|
||||
// Elements
|
||||
private VisualElement _background;
|
||||
private Foldout _foldoutLabel;
|
||||
private TextField _formulaEdit;
|
||||
private PropertyField _objectDictField;
|
||||
private PropertyField _typeDictField;
|
||||
private ScrollView _markerList;
|
||||
private Label _formulaPreview;
|
||||
|
||||
public override VisualElement CreatePropertyGUI(SerializedProperty property)
|
||||
{
|
||||
_property = property;
|
||||
_propertySourceDict = property.FindPropertyRelative(nameof(DynamicValue._serializedSources));
|
||||
_propertyTypeDict = property.FindPropertyRelative(nameof(DynamicValue._serializedTypes));
|
||||
_propertyFormula = property.FindPropertyRelative(nameof(DynamicValue._serializedFormula));
|
||||
_attribute = fieldInfo.GetCustomAttribute<DynamicValueAttribute>() ?? new DynamicValueAttribute();
|
||||
|
||||
var rootVisualElement = _visualTreeAsset.CloneTree();
|
||||
|
||||
// FIND ELEMENTS
|
||||
_background = rootVisualElement.Q("dv-background");
|
||||
_foldoutLabel = rootVisualElement.Q<Foldout>("dv-foldout-label");
|
||||
_foldoutLabel.text = property.displayName;
|
||||
_foldoutLabel.Q<Toggle>().RegisterValueChangedCallback(ev => TogglePreview(!ev.newValue));
|
||||
_formulaEdit = rootVisualElement.Q<TextField>("dv-formula-edit");
|
||||
_formulaEdit.BindProperty(_propertyFormula);
|
||||
_objectDictField = rootVisualElement.Q<PropertyField>("dv-sources-objects");
|
||||
_objectDictField.label = "";
|
||||
_objectDictField.BindProperty(_propertySourceDict);
|
||||
_objectDictField.TrackPropertyValue(_propertySourceDict, _ => OnSourceChanged());
|
||||
_typeDictField = rootVisualElement.Q<PropertyField>("dv-sources-types");
|
||||
_typeDictField.label = "";
|
||||
_typeDictField.BindProperty(_propertyTypeDict);
|
||||
_typeDictField.TrackPropertyValue(_propertyTypeDict, _ => OnSourceChanged());
|
||||
_markerList = rootVisualElement.Q("dv-markers").Q<ScrollView>();
|
||||
_formulaPreview = rootVisualElement.Q<Label>("dv-formula-preview");
|
||||
|
||||
// BACKGROUND HIGHLIGHT
|
||||
rootVisualElement.RegisterCallback<MouseEnterEvent>(_ => _background.visible = true);
|
||||
rootVisualElement.RegisterCallback<MouseLeaveEvent>(_ => _background.visible = false);
|
||||
|
||||
OnSourceChanged();
|
||||
|
||||
// DEFAULT STATE
|
||||
TogglePreview(!string.IsNullOrEmpty(_propertyFormula.stringValue));
|
||||
|
||||
return rootVisualElement;
|
||||
}
|
||||
|
||||
private void TogglePreview(bool show)
|
||||
{
|
||||
_formulaPreview.style.display = show ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
_background.style.display = show ? DisplayStyle.None : DisplayStyle.Flex;
|
||||
_foldoutLabel.SetValueWithoutNotify(!show);
|
||||
_formulaPreview.text = _propertyFormula.stringValue;
|
||||
|
||||
if (show) ValidateMarkers();
|
||||
}
|
||||
|
||||
private void ValidateMarkers()
|
||||
{
|
||||
var sourceList = GetSourceTypes();
|
||||
var invalidMarkers = new List<string>();
|
||||
|
||||
var value = (DynamicValue)_property.boxedValue;
|
||||
|
||||
// This is fix - when using SerializedReference via boxedValue it will always return null.
|
||||
// To prevent this we have to get manually SerializedReference values via SerializedProperty.
|
||||
// Also, we don't want to update serialized data!
|
||||
value._serializedTypes.Keys.ToList().ForEach((i, key) => value._serializedTypes[key] =
|
||||
_propertyTypeDict.FindPropertyRelative("_values").GetArrayElementAtIndex(i).boxedValue as SerializableType);
|
||||
|
||||
foreach (var marker in value.GetMarkers())
|
||||
{
|
||||
var color = "#23C552";
|
||||
|
||||
try
|
||||
{
|
||||
var sourceName = sourceList.Keys.FirstOrDefault(sourceName =>
|
||||
sourceName.Equals(marker.sourceMarker, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// Check if used marker uses source that is provided via serialization (via object or type)
|
||||
if (string.IsNullOrEmpty(sourceName))
|
||||
throw new DynamicValueInvalidMarkerException("SOURCE TYPE DO NOT EXIST");
|
||||
|
||||
// This will parse marker and throw exception if something wrong will occur
|
||||
marker.GetMembers();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is not DynamicValueInvalidMarkerException and not MemberNotFoundException) throw;
|
||||
Debug.LogException(e);
|
||||
color = "#F84F31";
|
||||
invalidMarkers.Add(marker.wholeMarker);
|
||||
}
|
||||
|
||||
_formulaPreview.text = _formulaPreview.text.Replace(marker.wholeMarker, $"<color={color}>{marker.wholeMarker}</color>");
|
||||
}
|
||||
|
||||
if (invalidMarkers.Any())
|
||||
{
|
||||
var errorBox = new HelpBox($"Found invalid markers: <b>{string.Join("</b>, <b>", invalidMarkers)}</b>.", HelpBoxMessageType.Error);
|
||||
errorBox.FadeOut(30000, 250, true);
|
||||
errorBox.RemoveOnClick();
|
||||
_foldoutLabel.InsertBefore(errorBox);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSourceChanged()
|
||||
{
|
||||
// CREATE SOURCE LIST
|
||||
switch (_attribute.type)
|
||||
{
|
||||
case DynamicValueType.BySerializedSources:
|
||||
_objectDictField.style.display = DisplayStyle.Flex;
|
||||
_typeDictField.style.display = DisplayStyle.None;
|
||||
break;
|
||||
case DynamicValueType.ByDynamicTypes:
|
||||
case DynamicValueType.ByStaticTypes:
|
||||
_typeDictField.SetEnabled(false);
|
||||
_objectDictField.style.display = DisplayStyle.None;
|
||||
_typeDictField.style.display = DisplayStyle.Flex;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
var allSourceTypes = GetSourceTypes();
|
||||
|
||||
// REMOVE MARKER LIST - remove changed and deleted ones
|
||||
_markerList.Children().ToList()
|
||||
.Where(markerElement => allSourceTypes.GetValueOrDefault(markerElement.name) != (Type)markerElement.userData)
|
||||
.ForEach(_markerList.Remove);
|
||||
|
||||
// CREATE MARKER LIST - add missing ones
|
||||
allSourceTypes
|
||||
.Where(pair => _markerList.Children().All(marker => marker.name != pair.Key))
|
||||
.Select(pair => CreateMarkerListElement(pair.Key, pair.Value))
|
||||
.ForEach(_markerList.Add);
|
||||
|
||||
if (!_foldoutLabel.value) ValidateMarkers();
|
||||
}
|
||||
|
||||
private VisualElement CreateMarkerListElement(string name, Type type, string path = "")
|
||||
{
|
||||
var foldout = new Foldout();
|
||||
foldout.text = $"<b>{name}</b> ({type.Name})";
|
||||
foldout.value = false;
|
||||
|
||||
// Path not available - first level marker - this string represent source
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
foldout.name = name;
|
||||
foldout.userData = type;
|
||||
path = name;
|
||||
}
|
||||
|
||||
var copyButton = new Button();
|
||||
copyButton.text = "Copy";
|
||||
copyButton.style.width = 50f;
|
||||
copyButton.style.unityTextAlign = TextAnchor.MiddleCenter;
|
||||
copyButton.style.paddingRight = 0;
|
||||
copyButton.style.paddingLeft = 0;
|
||||
copyButton.style.marginTop = 0;
|
||||
copyButton.style.marginBottom = 0;
|
||||
copyButton.clicked += () => EditorGUIUtility.systemCopyBuffer = $"{{{path}}}";
|
||||
copyButton.clicked += () => copyButton.text = "Copied!";
|
||||
copyButton.clicked += () => copyButton.schedule.Execute(() => copyButton.text = "Copy").ExecuteLater(3000);
|
||||
|
||||
var foldoutText = foldout.Q(className: "unity-foldout__text");
|
||||
foldoutText.style.flexShrink = 1;
|
||||
|
||||
var foldoutInput = foldout.Q(className: "unity-foldout__input");
|
||||
foldoutInput.style.maxWidth = Length.Percent(100f);
|
||||
foldoutInput.Add(copyButton);
|
||||
var membersToDisplay = MarkerParser.GetMembersForType(type, _attribute);
|
||||
|
||||
// BACKGROUND HIGHLIGHT
|
||||
foldoutInput.RegisterCallback<MouseEnterEvent>(_ => foldoutInput.style.backgroundColor = new Color(255,255,255,0.1f));
|
||||
foldoutInput.RegisterCallback<MouseLeaveEvent>(_ => foldoutInput.style.backgroundColor = StyleKeyword.Null);
|
||||
|
||||
// NO CHILDREN - hide extend/collapse arrow
|
||||
if (!membersToDisplay.Any()) foldout.Q("unity-checkmark").style.visibility = Visibility.Hidden;
|
||||
|
||||
// ONLY ONCE - when foldout extended then fill its content
|
||||
foldout.RegisterCallbackOnce<ChangeEvent<bool>>(_ =>
|
||||
{
|
||||
foreach (var member in membersToDisplay)
|
||||
{
|
||||
var parameters = MarkerParser.GetSubTypesInProvider(member, out var isProvider, out var isDynamic);
|
||||
if (isProvider)
|
||||
{
|
||||
// This is provider - it can have multiple parameters in addition to this marker
|
||||
parameters.ForEach(subType =>
|
||||
{
|
||||
var subName = $"{member.Name}[" + (isDynamic ? "---" : subType.Name)+ "]";
|
||||
var element = CreateMarkerListElement(subName, subType, $"{path}.{subName}");
|
||||
foldout.Add(element);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is just simple marker
|
||||
foldout.Add(CreateMarkerListElement($"{member.Name}", member.GetMemberValueType(), $"{path}.{member.Name}"));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// LITTLE FIX - dragger sometimes don't change its height
|
||||
foldout.RegisterValueChangedCallback(_ => _markerList.Q("unity-dragger").style.height = Length.Auto());
|
||||
|
||||
return foldout;
|
||||
}
|
||||
|
||||
private Dictionary<string, Type> GetSourceTypes()
|
||||
{
|
||||
switch (_attribute.type)
|
||||
{
|
||||
case DynamicValueType.BySerializedSources:
|
||||
var objKeyProps = _propertySourceDict.FindPropertyRelative("_keys");
|
||||
var objValueProps = _propertySourceDict.FindPropertyRelative("_values");
|
||||
return Enumerable.Range(0, objKeyProps.arraySize)
|
||||
.ToDictionary(
|
||||
i => objKeyProps.GetArrayElementAtIndex(i).boxedValue as string,
|
||||
i => objValueProps.GetArrayElementAtIndex(i).boxedValue.GetType()
|
||||
)
|
||||
.Where(pair => pair.Value != null)
|
||||
.ToDictionary(pair => pair.Key, pair => pair.Value);
|
||||
case DynamicValueType.ByDynamicTypes or DynamicValueType.ByStaticTypes:
|
||||
var typeKeyProps = _propertyTypeDict.FindPropertyRelative("_keys");
|
||||
var typeValueProps = _propertyTypeDict.FindPropertyRelative("_values");
|
||||
return Enumerable.Range(0, typeKeyProps.arraySize)
|
||||
.ToDictionary(
|
||||
i => typeKeyProps.GetArrayElementAtIndex(i).boxedValue as string,
|
||||
i => (typeValueProps.GetArrayElementAtIndex(i).boxedValue as SerializableType)?.type
|
||||
)
|
||||
.Where(pair => pair.Value != null)
|
||||
.ToDictionary(pair => pair.Key, pair => pair.Value);
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7310d287a63540638415ae227def94b9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences:
|
||||
- _visualTreeAsset: {fileID: 9197481963319205126, guid: e692746f57132b74aadeafd745f0f16e, type: 3}
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,84 @@
|
||||
#dv-background {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(255, 255, 255, 0.04);
|
||||
margin-left: -12px;
|
||||
margin-right: -12px;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#dv-foldout-label {
|
||||
}
|
||||
|
||||
#dv-foldout-label Foldout > Toggle {
|
||||
margin-left: 0;
|
||||
margin-top: 1px;
|
||||
margin-bottom: 1px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
#dv-formula-preview {
|
||||
padding-top: 1px;
|
||||
padding-right: 4px;
|
||||
padding-bottom: 1px;
|
||||
padding-left: 4px;
|
||||
border-top-width: 1px;
|
||||
border-right-width: 1px;
|
||||
border-bottom-width: 1px;
|
||||
border-left-width: 1px;
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-left-color: var(--unity-colors-helpbox-border);
|
||||
border-right-color: var(--unity-colors-helpbox-border);
|
||||
border-top-color: var(--unity-colors-helpbox-border);
|
||||
border-bottom-color: var(--unity-colors-helpbox-border);
|
||||
background-color: var(--unity-colors-helpbox-background);
|
||||
margin-left: 15px;
|
||||
min-height: 18px;
|
||||
}
|
||||
|
||||
#dv-formula-edit {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
#dv-formula-edit > TextInput {
|
||||
min-height: 45px;
|
||||
}
|
||||
|
||||
#dv-sources {
|
||||
margin-top: 5px;
|
||||
padding-right: 3px;
|
||||
padding-left: 3px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#dv-markers {
|
||||
margin-top: 5px;
|
||||
padding-left: 3px;
|
||||
padding-right: 3px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.border {
|
||||
border-top-left-radius: 2px;
|
||||
border-bottom-left-radius: 2px;
|
||||
border-top-right-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
border-right-width: 1px;
|
||||
border-top-width: 1px;
|
||||
border-bottom-width: 1px;
|
||||
border-left-width: 1px;
|
||||
border-left-color: rgb(0, 0, 0);
|
||||
border-right-color: rgb(0, 0, 0);
|
||||
border-top-color: rgb(0, 0, 0);
|
||||
border-bottom-color: rgb(0, 0, 0);
|
||||
}
|
||||
|
||||
.unity-base-field {
|
||||
margin-right: 0;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 683dc949e5b79274f8fbc2bc650fd84b
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||
disableValidation: 0
|
||||
@@ -0,0 +1,17 @@
|
||||
<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/DynamicValues/Editor/DynamicValueDrawer.uss?fileID=7433441132597879392&guid=683dc949e5b79274f8fbc2bc650fd84b&type=3#DynamicValueDrawer" />
|
||||
<ui:VisualElement name="dv-root">
|
||||
<ui:VisualElement name="dv-background" />
|
||||
<ui:Foldout text="{property_name}" name="dv-foldout-label">
|
||||
<ui:TextField placeholder-text="Formula here" multiline="true" name="dv-formula-edit" />
|
||||
<ui:Foldout text="Sources:" name="dv-sources" class="border">
|
||||
<uie:PropertyField name="dv-sources-objects" />
|
||||
<uie:PropertyField name="dv-sources-types" />
|
||||
</ui:Foldout>
|
||||
<ui:Foldout text="Markers:" name="dv-markers" class="border">
|
||||
<ui:ScrollView name="dv-markers-list" horizontal-scroller-visibility="Hidden" vertical-scroller-visibility="AlwaysVisible" />
|
||||
</ui:Foldout>
|
||||
</ui:Foldout>
|
||||
<ui:Label text="{preview_formula}" name="dv-formula-preview" />
|
||||
</ui:VisualElement>
|
||||
</ui:UXML>
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e692746f57132b74aadeafd745f0f16e
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "RPGCoreCommon.DynamicValues.Editor",
|
||||
"rootNamespace": "RPGCoreCommon.DynamicValues.Editor",
|
||||
"references": [
|
||||
"RPGCoreCommon.DynamicValues",
|
||||
"RPGCoreCommon.Helpers.Editor",
|
||||
"RPGCoreCommon.Helpers"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 63d26f6c5e724ba085544f845889ee92
|
||||
timeCreated: 1759334877
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9decfc31d224dcd8d04e812ca9b1f20
|
||||
timeCreated: 1759333999
|
||||
@@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using RPGCoreCommon.Helpers;
|
||||
using RPGCoreCommon.Helpers.CustomTypes;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace RPGCoreCommon.DynamicValues
|
||||
{
|
||||
[Serializable]
|
||||
public class DynamicValue
|
||||
{
|
||||
// User defined formula or text with markers
|
||||
[SerializeField] internal string _serializedFormula;
|
||||
[SerializeField] internal SerializableDictionary<string, SerializableType> _serializedTypes = new();
|
||||
[SerializeField] internal SerializableDictionary<string, Object> _serializedSources = new();
|
||||
|
||||
private bool _isParsed;
|
||||
private string _formulaFormat;
|
||||
private MarkerParser[] _markers;
|
||||
|
||||
private void Parse()
|
||||
{
|
||||
if (Application.isPlaying && _isParsed) return;
|
||||
|
||||
_isParsed = true;
|
||||
|
||||
var matches = new Regex("{[a-zA-z0-9-\\.]*}").Matches(_serializedFormula);
|
||||
_markers = new MarkerParser[matches.Count];
|
||||
_formulaFormat = _serializedFormula;
|
||||
|
||||
for (var i = matches.Count-1; i >= 0; i--)
|
||||
{
|
||||
_markers[i] = new MarkerParser(matches[i].Value);
|
||||
_markers[i].sourceType = GetSourceType(_markers[i].sourceMarker);
|
||||
_formulaFormat = _formulaFormat.Remove(matches[i].Index, matches[i].Length).Insert(matches[i].Index, $"{{{i}}}");
|
||||
}
|
||||
}
|
||||
|
||||
private Type GetSourceType(string sourceMarker)
|
||||
{
|
||||
if (_serializedTypes.TryGetValue(sourceMarker, out var type)) return type;
|
||||
if (_serializedSources.TryGetValue(sourceMarker, out var obj)) return obj.GetType();
|
||||
return null;
|
||||
}
|
||||
|
||||
private object GetSource(string sourceMarker, DynamicValueSourceContext sourceContext = null)
|
||||
{
|
||||
if (_serializedSources.TryGetValue(sourceMarker, out var s1)) return s1;
|
||||
if (sourceContext != null && sourceContext.TryGetSource(sourceMarker, out var s2)) return s2;
|
||||
return null;
|
||||
}
|
||||
|
||||
public void RemoveDynamicTypes()
|
||||
{
|
||||
_serializedTypes.Clear();
|
||||
}
|
||||
|
||||
public void SetDynamicTypes(Dictionary<string, Type> types)
|
||||
{
|
||||
RemoveDynamicTypes();
|
||||
types.DictForEach(SetDynamicType);
|
||||
}
|
||||
|
||||
public void SetDynamicType(string name, Type type)
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
Debug.LogError($"method '{nameof(SetDynamicType)}' is only available in editor. Dynamic types should not be changed runtime!");
|
||||
return;
|
||||
}
|
||||
|
||||
_serializedTypes[name] = new SerializableType(type);
|
||||
}
|
||||
|
||||
public MarkerParser[] GetMarkers()
|
||||
{
|
||||
Parse();
|
||||
return _markers.ToArray();
|
||||
}
|
||||
|
||||
public string GetString(DynamicValueSourceContext sourceContext = null)
|
||||
{
|
||||
Parse();
|
||||
var values = _markers
|
||||
.Select(marker => marker.GetValue(GetSource(marker.sourceMarker, sourceContext)))
|
||||
.ToArray<object>();
|
||||
return string.Format(_formulaFormat, values);
|
||||
}
|
||||
|
||||
public int GetValue(DynamicValueSourceContext sourceContext = null)
|
||||
{
|
||||
Parse();
|
||||
ExpressionEvaluator.Evaluate(GetString(sourceContext), out int value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d3e7897496b64571880ab2ffe679fa43
|
||||
timeCreated: 1759334381
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace RPGCoreCommon.DynamicValues
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class DynamicValueAttribute : PropertyAttribute
|
||||
{
|
||||
internal DynamicValueType type { private set; get; }
|
||||
internal List<Type> staticTypes { private set; get; }
|
||||
|
||||
internal List<Type> ignoredDeclaringTypes { private set; get; } = new()
|
||||
{
|
||||
typeof(Object),
|
||||
typeof(Behaviour),
|
||||
typeof(MonoBehaviour),
|
||||
typeof(Component)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DynamicValue"/> will be managed via code (adding/removing in inspector is)
|
||||
/// </summary>
|
||||
public DynamicValueAttribute(DynamicValueType type = DynamicValueType.BySerializedSources, params Type[] staticTypes)
|
||||
{
|
||||
this.type = type;
|
||||
if (type is DynamicValueType.ByStaticTypes) this.staticTypes = staticTypes.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0eb1b79e28da4cff8d0554fd6b270f26
|
||||
timeCreated: 1759335563
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RPGCoreCommon.DynamicValues
|
||||
{
|
||||
public class DynamicValueExistingTypeException : UnityException
|
||||
{
|
||||
public DynamicValueExistingTypeException(string text) : base(text)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 40c9343cc5534008aecf4a1734171915
|
||||
timeCreated: 1759685699
|
||||
@@ -0,0 +1,11 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace RPGCoreCommon.DynamicValues
|
||||
{
|
||||
public class DynamicValueInvalidMarkerException : UnityException
|
||||
{
|
||||
public DynamicValueInvalidMarkerException(string text) : base(text)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 902c960012c84504a20e452871dadc15
|
||||
timeCreated: 1760275028
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RPGCoreCommon.DynamicValues
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Method)]
|
||||
public class DynamicValueProviderAttribute : PropertyAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// <b>[Fields/Property]</b><br/>
|
||||
/// Marks non-public field or property to provide value for <see cref="DynamicValue"/>'s formula.
|
||||
/// Public fields and properties are always used as value providers.
|
||||
/// Collections are ALWAYS ignored - if you want to get values from collection try to use custom method. More info below.
|
||||
/// <br/><br/>
|
||||
/// <b>[Method]</b><br/>
|
||||
/// Method can be marked as custom value provider.
|
||||
/// Those methods can have any name, but require specific return value and one parameter.
|
||||
/// Only two definitions of methods are valid, ex.:
|
||||
/// <ul>
|
||||
/// <li><b>T CustomProviderMethod(Type)</b> - this usage supports polymorphism,
|
||||
/// type in parameter will be supplied with type of any class that implements <T>.
|
||||
/// Thanks to this type of value we want can be specified in marker.</li>
|
||||
/// <li><b>T CustomProviderMethod(string)</b> - this usage DOESN'T support polymorphism,
|
||||
/// string in parameter is exactly value specified in marker.</li>
|
||||
/// </ul>
|
||||
/// </summary>
|
||||
public DynamicValueProviderAttribute()
|
||||
{
|
||||
// TODO: tutaj jeszcze jako argument trzeba przekazać nazwę drugiej metody, która ma listę możliwych stringów w przypadku użycia na CustomProviderMethod(string), wtedy będzie można to fajnie walidować już na poziomie tego narzędzia
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b49ba0d446154c368a559d69b8edf97b
|
||||
timeCreated: 1763033238
|
||||
@@ -0,0 +1,30 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace RPGCoreCommon.DynamicValues
|
||||
{
|
||||
public class DynamicValueSourceContext
|
||||
{
|
||||
private readonly Dictionary<string, object> _sources = new();
|
||||
|
||||
public void SetSource(string name, object source)
|
||||
{
|
||||
_sources.Remove(name.ToLower());
|
||||
_sources.Add(name.ToLower(), source);
|
||||
}
|
||||
|
||||
public void RemoveResource(string name)
|
||||
{
|
||||
_sources.Remove(name.ToLower());
|
||||
}
|
||||
|
||||
public void RemoveSources()
|
||||
{
|
||||
_sources.Clear();
|
||||
}
|
||||
|
||||
public bool TryGetSource(string name, out object source)
|
||||
{
|
||||
return _sources.TryGetValue(name.ToLower(), out source);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c7ffbb14acb7403a982719f0771d95f4
|
||||
timeCreated: 1763657503
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace RPGCoreCommon.DynamicValues
|
||||
{
|
||||
public enum DynamicValueType
|
||||
{
|
||||
/// <summary>
|
||||
/// [Default option] Sources defined by user in editor.
|
||||
/// </summary>
|
||||
BySerializedSources,
|
||||
|
||||
/// <summary>
|
||||
/// Types of sources defined runtime.
|
||||
/// Usually when other field can define what types will be used.
|
||||
/// Sources needs to be added later in runtime.
|
||||
/// </summary>
|
||||
ByDynamicTypes,
|
||||
|
||||
/// <summary>
|
||||
/// Types of sources defined via attribute.
|
||||
/// Sources needs to be added later in runtime.
|
||||
/// </summary>
|
||||
ByStaticTypes
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dc2e7b666a654564ae758ccb7c7b2571
|
||||
timeCreated: 1762786038
|
||||
@@ -0,0 +1,8 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("RPGCoreCommon.DynamicValues.Editor")]
|
||||
|
||||
namespace RPGCoreCommon.DynamicValues
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 77ee6ce56165430e8dbf117a46d1182f
|
||||
timeCreated: 1759337937
|
||||
@@ -0,0 +1,231 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using RPGCoreCommon.Helpers;
|
||||
using RPGCoreCommon.Helpers.Exceptions;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RPGCoreCommon.DynamicValues
|
||||
{
|
||||
public class MarkerParser
|
||||
{
|
||||
private static readonly BindingFlags BindingFlags = BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly;
|
||||
|
||||
public string wholeMarker { get; private set; }
|
||||
public string sourceMarker { get; private set; }
|
||||
|
||||
internal Type sourceType;
|
||||
private MemberInfo[] _members;
|
||||
private object[] _membersParameters;
|
||||
private string[] _markers;
|
||||
private string[] _parameters;
|
||||
|
||||
internal MarkerParser(string wholeMarker)
|
||||
{
|
||||
this.wholeMarker = wholeMarker;
|
||||
var tempMarker = wholeMarker.Trim('{', '}').Split('.');
|
||||
|
||||
// Source marker should be always present (even if not it will be as empty string)
|
||||
sourceMarker = tempMarker[0];
|
||||
|
||||
if (tempMarker.Length <= 1) return;
|
||||
|
||||
_markers = new string[tempMarker.Length - 1];
|
||||
_parameters = new string[tempMarker.Length - 1];
|
||||
|
||||
for (var i = 1; i < tempMarker.Length; i++)
|
||||
{
|
||||
var valueMarker = tempMarker[i];
|
||||
var tempValueMarker = valueMarker.TrimEnd(']').Split('[');
|
||||
_markers[i - 1] = tempValueMarker[0];
|
||||
if (tempValueMarker.Length > 1) _parameters[i - 1] = tempValueMarker[1];
|
||||
}
|
||||
}
|
||||
|
||||
/// <param name="type">Type that will be scanned</param>
|
||||
/// <param name="attribute">Attribute with settings for this instance of <see cref="DynamicValue"/></param>
|
||||
/// <returns>List of members that can be used as value providers for <see cref="DynamicValue"/>'s formula</returns>
|
||||
internal static List<MemberInfo> GetMembersForType(Type type, DynamicValueAttribute attribute)
|
||||
{
|
||||
return type.GetAllTypes()
|
||||
.SelectMany(t => t.GetMembers(BindingFlags))
|
||||
.Where(member =>
|
||||
{
|
||||
if (attribute.ignoredDeclaringTypes.Contains(member.DeclaringType)) return false;
|
||||
if (member is not MethodInfo and not PropertyInfo and not FieldInfo) return false;
|
||||
var isProvider = member.GetCustomAttribute<DynamicValueProviderAttribute>() != null;
|
||||
return isProvider || (member.IsMemberReadPublic() && member is not MethodInfo);
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
/// <param name="member">Member to check if it can provide any subtypes besides itself</param>
|
||||
/// <param name="isProvider">TRUE whenever given member is provider for <see cref="DynamicValue"/>'s formula</param>
|
||||
/// <param name="isDynamic">TRUE if this member has parameter, but not specified with type, only by user's string</param>
|
||||
/// <returns>All subtypes that this member can provide. If type can't be specified then it is dynamic.</returns>
|
||||
internal static List<Type> GetSubTypesInProvider(MemberInfo member, out bool isProvider, out bool isDynamic)
|
||||
{
|
||||
isProvider = true;
|
||||
isDynamic = false;
|
||||
var providerAttribute = member.GetCustomAttribute<DynamicValueProviderAttribute>();
|
||||
var methodInfo = member as MethodInfo;
|
||||
|
||||
// Only Methods support parameters, but only when attribute is also given
|
||||
if (methodInfo == null || providerAttribute == null)
|
||||
{
|
||||
isProvider = false;
|
||||
return new List<Type>();
|
||||
}
|
||||
|
||||
var parameter = methodInfo.GetParameters().FirstOrDefault();
|
||||
var isStringParameter = parameter?.ParameterType == typeof(string);
|
||||
var isTypeParameter = parameter?.ParameterType == typeof(Type);
|
||||
|
||||
// BAD - invalid parameter definition in method!
|
||||
if ((!isStringParameter && !isTypeParameter) || methodInfo.GetParameters().Length != 1)
|
||||
{
|
||||
Debug.LogError($"<b>{member.Name}</b> has invalid definition!<br>" +
|
||||
$"[{nameof(DynamicValueProviderAttribute)}] Supports only methods with ONE parameter that is <b>string</b> or <b>type</b>!");
|
||||
return new List<Type>();
|
||||
}
|
||||
|
||||
// BAD - return type is required!
|
||||
if (methodInfo.ReturnType == typeof(void))
|
||||
{
|
||||
Debug.LogError($"<b>{member.Name}</b> has invalid definition!<br>" +
|
||||
$"[{nameof(DynamicValueProviderAttribute)}] Supports only methods with defined return type!");
|
||||
return new List<Type>();
|
||||
}
|
||||
|
||||
// GOOD by string - polymorphism not supported - parameter is dynamic, which means text can have anything
|
||||
if (isStringParameter)
|
||||
{
|
||||
isDynamic = true;
|
||||
return new List<Type> { methodInfo.ReturnType };
|
||||
}
|
||||
|
||||
// GOOD by type - polymorphism supported
|
||||
return UnityEditor.TypeCache.GetTypesDerivedFrom(methodInfo.ReturnType)
|
||||
.Where(type => !type.IsAbstract && !type.IsGenericType)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <returns>Type of value that this marker leads to</returns>
|
||||
/// <exception cref="MemberNotFoundException">When member at path could not be found</exception>
|
||||
public Type GetValueType()
|
||||
{
|
||||
return GetMembers().Last().GetMemberValueType();
|
||||
}
|
||||
|
||||
public string GetValue(object source)
|
||||
{
|
||||
// This will also refill _members and _membersParameters
|
||||
GetMembers();
|
||||
|
||||
var tempObject = source;
|
||||
for (var i = 0; i < _members.Length; i++)
|
||||
{
|
||||
if (_members[i] is FieldInfo or PropertyInfo)
|
||||
tempObject = _members[i].GetMemberValue(tempObject);
|
||||
else if (_members[i] is MethodInfo methodInfo)
|
||||
tempObject = methodInfo.Invoke(tempObject, new[] { _membersParameters[i] });
|
||||
}
|
||||
|
||||
return tempObject.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns chain of <see cref="MemberInfo"/> that will be used to get value for this marker.
|
||||
/// </summary>
|
||||
/// <returns>Array of <see cref="MemberInfo"/> used by this marker.</returns>
|
||||
/// <exception cref="MemberNotFoundException">When given path from marker doesn't exist in source.</exception>
|
||||
public MemberInfo[] GetMembers()
|
||||
{
|
||||
if (Application.isPlaying && _members != null) return _members.ToArray();
|
||||
|
||||
_members = new MemberInfo[_markers.Length];
|
||||
_membersParameters = new object[_markers.Length];
|
||||
|
||||
var tempType = sourceType;
|
||||
for (var i = 0; i < _markers.Length; i++)
|
||||
{
|
||||
var member = tempType.GetAllTypes()
|
||||
.SelectMany(t => t.GetMember(_markers[i], BindingFlags)).FirstOrDefault();
|
||||
|
||||
// BAD - whole member doesn't exist
|
||||
if (member == null)
|
||||
throw new MemberNotFoundException($"Member [{_markers[i]}] is not found!", tempType);
|
||||
|
||||
var attribute = member.GetCustomAttribute<DynamicValueProviderAttribute>();
|
||||
|
||||
// BAD - non-public member without attribute
|
||||
if (!member.IsMemberReadPublic() && attribute is null)
|
||||
throw new MemberNotFoundException($"Member [{_markers[i]}] found, but its non-public without {nameof(DynamicValueProviderAttribute)}!", tempType);
|
||||
|
||||
// BAD - member is collection, collections aren't supported
|
||||
if (typeof(Enumerable).IsAssignableFrom(member.GetMemberValueType()))
|
||||
throw new MemberNotFoundException($"Member [{_markers[i]}] found, but it is collection. Collections aren't supported!", tempType);
|
||||
|
||||
tempType = member.GetMemberValueType();
|
||||
|
||||
if (member is not MethodInfo methodInfo)
|
||||
{
|
||||
// BAD - simple non-collection found, but marker has parameter
|
||||
if (_parameters[i] != null)
|
||||
throw new MemberNotFoundException($"Member [{_markers[i]}] found, but marker has parameter. Only methods can have parameters!", tempType);
|
||||
|
||||
// GOOD - simple non-collection field or property
|
||||
_members[i] = member;
|
||||
continue;
|
||||
}
|
||||
|
||||
// BAD - is method, but without required attribute
|
||||
if (attribute is null)
|
||||
throw new MemberNotFoundException($"Member [{_markers[i]}] found, but without required {nameof(DynamicValueProviderAttribute)}!", tempType);
|
||||
|
||||
var validReturnType = methodInfo.ReturnType != typeof(void);
|
||||
var parameter = methodInfo.GetParameters().FirstOrDefault();
|
||||
var isStringParameter = parameter?.ParameterType == typeof(string);
|
||||
var isTypeParameter = parameter?.ParameterType == typeof(Type);
|
||||
|
||||
// BAD - provider method has invalid definition
|
||||
if (!validReturnType || (!isStringParameter && !isTypeParameter))
|
||||
throw new MemberNotFoundException($"Member [{_markers[i]}] found, but definition is invalid! Must contain only one parameter <b>string</b> or <b>Type</b> and also must return something.", tempType);
|
||||
|
||||
_members[i] = methodInfo;
|
||||
|
||||
// GOOD - parameter is string, which means it is dynamic, only body of method can use it.
|
||||
// Polymorphism is NOT supported here, return value is always method's return type
|
||||
if (isStringParameter)
|
||||
{
|
||||
_membersParameters[i] = _parameters[i];
|
||||
tempType = methodInfo.ReturnType;
|
||||
continue;
|
||||
}
|
||||
|
||||
// GOOD - parameter is Type, which we can match by its name.
|
||||
// Polymorphism is supported here, dynamic value know type referenced by marker.
|
||||
tempType = UnityEditor.TypeCache.GetTypesDerivedFrom(methodInfo.ReturnType)
|
||||
.FirstOrDefault(t => t.Name.Equals(_parameters[i], StringComparison.OrdinalIgnoreCase));
|
||||
_membersParameters[i] = tempType;
|
||||
}
|
||||
|
||||
return _members.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="GetMembers()"/>
|
||||
/// </summary>
|
||||
/// <param name="membersParameters">
|
||||
/// <b>[Only when MethodInfo]</b> List of parameters that will be used for specific member.
|
||||
/// Can be <b>string</b> or <b>Type</b>.
|
||||
/// </param>
|
||||
/// <inheritdoc cref="GetMembers()"/>
|
||||
public MemberInfo[] GetMembers(out object[] membersParameters)
|
||||
{
|
||||
var members = GetMembers();
|
||||
membersParameters = _membersParameters.ToArray();
|
||||
return members;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 99a9d00853004ff788e659a257298db0
|
||||
timeCreated: 1763109073
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "RPGCoreCommon.DynamicValues",
|
||||
"rootNamespace": "RPGCoreCommon.DynamicValues",
|
||||
"references": [
|
||||
"RPGCoreCommon.Helpers"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ade6d5973db3446ab20f0bb7e4a872f8
|
||||
timeCreated: 1759334847
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a47adc904b054d1f83334b894a8dac0d
|
||||
timeCreated: 1759334613
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 079fed2826f14a7da75cd8d7d84b67f6
|
||||
timeCreated: 1759334623
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e35c80ecac124ccfa0a1124809dd8dde
|
||||
timeCreated: 1762190736
|
||||
@@ -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&guid=215036bf11e09364a9da52f92c9534f0&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="<-- 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
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 39ab4f14276441b6afcbfcdf67f90a28
|
||||
timeCreated: 1762713444
|
||||
@@ -0,0 +1,21 @@
|
||||
using RPGCoreCommon.Helpers.PropertyAttributeDrawers;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace RPGCoreCommon.Helpers.Editor.PropertyAttributeDrawers
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(LayerAttribute))]
|
||||
public class LayerDrawer : PropertyDrawer
|
||||
{
|
||||
public override VisualElement CreatePropertyGUI(SerializedProperty property)
|
||||
{
|
||||
if (property.boxedValue is not int)
|
||||
return new Label($"Field '{property.displayName}' is not INTEGER. {nameof(LayerAttribute)} can be used only on <b>INTEGER</b> field.");
|
||||
|
||||
var layerField = new LayerField(property.displayName);
|
||||
layerField.BindProperty(property);
|
||||
return layerField;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a3af142730740288c3078e34cd62718
|
||||
timeCreated: 1762713459
|
||||
@@ -0,0 +1,21 @@
|
||||
using RPGCoreCommon.Helpers.PropertyAttributeDrawers;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace RPGCoreCommon.Helpers.Editor.PropertyAttributeDrawers
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(LayerMaskAttribute))]
|
||||
public class LayerMaskDrawer : PropertyDrawer
|
||||
{
|
||||
public override VisualElement CreatePropertyGUI(SerializedProperty property)
|
||||
{
|
||||
if (property.boxedValue is not int)
|
||||
return new Label($"Field '{property.displayName}' is not INTEGER. {nameof(LayerMaskAttribute)} can be used only on <b>INTEGER</b> field.");
|
||||
|
||||
var layerMaskField = new LayerMaskField(property.displayName);
|
||||
layerMaskField.BindProperty(property);
|
||||
return layerMaskField;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a1a4f0b89e344d8a9aefc5767fdb860c
|
||||
timeCreated: 1762714412
|
||||
@@ -0,0 +1,19 @@
|
||||
using RPGCoreCommon.Helpers.PropertyAttributeDrawers;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace RPGCoreCommon.Helpers.Editor.PropertyAttributeDrawers
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
|
||||
public class ReadOnlyDrawer : PropertyDrawer
|
||||
{
|
||||
public override VisualElement CreatePropertyGUI(SerializedProperty property)
|
||||
{
|
||||
var propertyField = new PropertyField(property);
|
||||
propertyField.name = "ReadOnly";
|
||||
propertyField.RegisterCallback<GeometryChangedEvent>(_ => propertyField.SetEnabled(false));
|
||||
return propertyField;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d6de1b490a8549d3a67957f5a2a8a03e
|
||||
timeCreated: 1764603191
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using RPGCoreCommon.Helpers.PropertyAttributeDrawers;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace RPGCoreCommon.Helpers.Editor.PropertyAttributeDrawers
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(SerializeReferenceHelperAttribute))]
|
||||
public class SerializeReferenceHelperDrawer : PropertyDrawer
|
||||
{
|
||||
public override VisualElement CreatePropertyGUI(SerializedProperty property)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9ffb1bb18c6d4a7a889c32c17d78a39c
|
||||
timeCreated: 1773956371
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "RPGCoreCommon.Helpers.Editor",
|
||||
"rootNamespace": "RPGCoreCommon.Helpers.Editor",
|
||||
"references": [
|
||||
"RPGCoreCommon.Helpers"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4506747573fa44a9877e5bf69f5bb4a8
|
||||
timeCreated: 1759335131
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fc2af81cae08451abf2593fd5fb18538
|
||||
timeCreated: 1761410197
|
||||
@@ -0,0 +1,66 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace RPGCoreCommon.Helpers.Editor.UIElements
|
||||
{
|
||||
[UxmlElement]
|
||||
public partial class FakeObjectField : VisualElement
|
||||
{
|
||||
private Image _objectImage;
|
||||
private Label _objectLabel;
|
||||
|
||||
public FakeObjectField() : this(null) { }
|
||||
|
||||
public FakeObjectField(string labelText = "")
|
||||
{
|
||||
var field = new VisualElement();
|
||||
field.AddToClassList("unity-object-field");
|
||||
field.AddToClassList("unity-base-field");
|
||||
field.AddToClassList("unity-base-field__aligned");
|
||||
field.style.marginLeft = field.style.marginRight = 3;
|
||||
|
||||
if (!string.IsNullOrEmpty(labelText))
|
||||
{
|
||||
var label = new Label(labelText);
|
||||
label.AddToClassList("unity-label");
|
||||
label.AddToClassList("unity-base-field__label");
|
||||
field.Add(label);
|
||||
}
|
||||
|
||||
var fieldInput = new VisualElement();
|
||||
fieldInput.AddToClassList("unity-base-field__input");
|
||||
fieldInput.AddToClassList("unity-object-field__input");
|
||||
field.Add(fieldInput);
|
||||
|
||||
var fieldObject = new VisualElement();
|
||||
fieldObject.AddToClassList("unity-object-field__object");
|
||||
fieldInput.Add(fieldObject);
|
||||
|
||||
_objectImage = new Image();
|
||||
_objectImage.AddToClassList("unity-object-field-display__icon");
|
||||
fieldObject.Add(_objectImage);
|
||||
|
||||
_objectLabel = new Label();
|
||||
_objectLabel.AddToClassList("unity-object-field-display__label");
|
||||
fieldObject.Add(_objectLabel);
|
||||
|
||||
var fieldSelector = new VisualElement();
|
||||
fieldSelector.AddToClassList("unity-object-field__selector");
|
||||
fieldInput.Add(fieldSelector);
|
||||
|
||||
Add(field);
|
||||
}
|
||||
|
||||
public void Set(string text, Texture image)
|
||||
{
|
||||
_objectLabel.text = text;
|
||||
_objectImage.image = image;
|
||||
}
|
||||
|
||||
public void Unset()
|
||||
{
|
||||
_objectLabel.text = null;
|
||||
_objectImage.image = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 42c0552c878947b394d70f8745d0ec3b
|
||||
timeCreated: 1762538499
|
||||
@@ -0,0 +1,9 @@
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace RPGCoreCommon.Helpers.Editor.UIElements
|
||||
{
|
||||
[UxmlElement]
|
||||
public partial class InspectorElement : UnityEditor.UIElements.InspectorElement
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2a2d499847854192a17c936d66e234b7
|
||||
timeCreated: 1761410271
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user