init
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user