mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 13:01:52 +00:00
ScriptCore: Show Custom Editor
Implemented basic Dictionary editor
This commit is contained in:
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GlitchyEngine.Editor;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true)]
|
||||||
|
public sealed class CustomEditorAttribute : Attribute
|
||||||
|
{
|
||||||
|
public Type Type { get; private set; }
|
||||||
|
|
||||||
|
public CustomEditorAttribute(Type type)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
using GlitchyEngine.Extensions;
|
||||||
|
using ImGuiNET;
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace GlitchyEngine.Editor;
|
||||||
|
|
||||||
|
[CustomEditor(typeof(Dictionary<,>))]
|
||||||
|
public class DictionaryEditor
|
||||||
|
{
|
||||||
|
private static object? dictionaryForNewValue = null;
|
||||||
|
|
||||||
|
public static object? ShowEditor(object? reference, Type fieldType, string fieldName)
|
||||||
|
{
|
||||||
|
object newDictionary = EntityEditor.DidNotChange;
|
||||||
|
IDictionary? dictionary = reference as IDictionary;
|
||||||
|
|
||||||
|
Type keyType = fieldType.GetGenericArguments()[0];
|
||||||
|
Type valueType = fieldType.GetGenericArguments()[1];
|
||||||
|
|
||||||
|
// The buttons should always be visible -> save whether the node is open
|
||||||
|
// If we have no instance, the user shouldn't be able to open the list
|
||||||
|
bool listOpen = ImGui.TreeNodeEx(fieldName, ImGuiTreeNodeFlags.AllowOverlap | ImGuiTreeNodeFlags.SpanFullWidth |
|
||||||
|
(dictionary == null ? ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.NoTreePushOnOpen : ImGuiTreeNodeFlags.Framed));
|
||||||
|
|
||||||
|
if (dictionary == null)
|
||||||
|
listOpen = false;
|
||||||
|
|
||||||
|
var addButtonWidth = ImGui.CalcTextSize("+").X + 2 * ImGui.GetStyle().FramePadding.X;
|
||||||
|
var removeButtonWidth = ImGui.CalcTextSize("-").X + 2 * ImGui.GetStyle().FramePadding.X;
|
||||||
|
|
||||||
|
ImGui.SameLine(ImGui.GetWindowContentRegionMax().X - addButtonWidth - ImGui.GetStyle().FramePadding.X);
|
||||||
|
|
||||||
|
if (ImGui.SmallButton("+"))
|
||||||
|
{
|
||||||
|
if (dictionary == null)
|
||||||
|
{
|
||||||
|
newDictionary = Activator.CreateInstance(fieldType);
|
||||||
|
dictionary = newDictionary as IDictionary;
|
||||||
|
|
||||||
|
Debug.Assert(dictionary != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
dictionaryForNewValue = dictionary;
|
||||||
|
|
||||||
|
// TODO: show fake entry!
|
||||||
|
|
||||||
|
//object? newKey = ActivatorExtension.CreateInstanceSafe(keyType);
|
||||||
|
//object? newValue = ActivatorExtension.CreateInstanceSafe(valueType);
|
||||||
|
|
||||||
|
//if (newKey != null)
|
||||||
|
// dictionary!.Add(newKey, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiExtension.AttachTooltip("Add a new Element to the dictionary.");
|
||||||
|
|
||||||
|
if (listOpen)
|
||||||
|
{
|
||||||
|
Debug.Assert(dictionary != null, "Opened tree node even though it should have been a leaf!");
|
||||||
|
|
||||||
|
int id = 0;
|
||||||
|
|
||||||
|
List<DictionaryEntry> newEntries = new();
|
||||||
|
List<object> keysToDelete = new();
|
||||||
|
|
||||||
|
foreach (DictionaryEntry entry in dictionary!)
|
||||||
|
{
|
||||||
|
id++;
|
||||||
|
ImGui.PushID(id);
|
||||||
|
|
||||||
|
bool isEntryOpen = ImGui.TreeNode("");
|
||||||
|
|
||||||
|
ImGui.SameLine(ImGui.GetWindowContentRegionMax().X - removeButtonWidth);
|
||||||
|
|
||||||
|
if (ImGui.SmallButton("-"))
|
||||||
|
{
|
||||||
|
keysToDelete.Add(entry.Key!);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiExtension.AttachTooltip("Remove the last Element from the list.");
|
||||||
|
|
||||||
|
if (isEntryOpen)
|
||||||
|
{
|
||||||
|
object newKey = EntityEditor.ShowFieldEditor(entry.Key, entry.Key?.GetType() ?? keyType, "Key");
|
||||||
|
|
||||||
|
if (newKey != EntityEditor.DidNotChange)
|
||||||
|
{
|
||||||
|
keysToDelete.Add(entry.Key!);
|
||||||
|
newEntries.Add(new DictionaryEntry(newKey, entry.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
object newValue = EntityEditor.ShowFieldEditor(entry.Value, entry.Value?.GetType() ?? valueType, "Value");
|
||||||
|
|
||||||
|
if (newValue != EntityEditor.DidNotChange)
|
||||||
|
{
|
||||||
|
keysToDelete.Add(entry.Key!);
|
||||||
|
newEntries.Add(new DictionaryEntry(entry.Key!, newValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TreePop();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PopID();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (object key in keysToDelete)
|
||||||
|
{
|
||||||
|
dictionary.Remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (DictionaryEntry newEntry in newEntries)
|
||||||
|
{
|
||||||
|
dictionary.Add(newEntry.Key, newEntry.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TreePop();
|
||||||
|
}
|
||||||
|
|
||||||
|
return newDictionary;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ using System.Text;
|
|||||||
using GlitchyEngine.Core;
|
using GlitchyEngine.Core;
|
||||||
using GlitchyEngine.Extensions;
|
using GlitchyEngine.Extensions;
|
||||||
using GlitchyEngine.Math;
|
using GlitchyEngine.Math;
|
||||||
|
using GlitchyEngine.Serialization;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
||||||
namespace GlitchyEngine.Editor;
|
namespace GlitchyEngine.Editor;
|
||||||
@@ -20,7 +21,63 @@ class DidNotChange
|
|||||||
|
|
||||||
internal class EntityEditor
|
internal class EntityEditor
|
||||||
{
|
{
|
||||||
private static readonly DidNotChange DidNotChange = new DidNotChange();
|
public static readonly DidNotChange DidNotChange = new DidNotChange();
|
||||||
|
|
||||||
|
private delegate object? ShowCustomEditorMethod(object reference, Type fieldType, string fieldName);
|
||||||
|
|
||||||
|
private static Dictionary<Type, ShowCustomEditorMethod> _customEditors = new();
|
||||||
|
|
||||||
|
static EntityEditor()
|
||||||
|
{
|
||||||
|
foreach (Type type in TypeExtension.EnumerateAllTypes())
|
||||||
|
{
|
||||||
|
if (type.TryGetCustomAttribute<CustomEditorAttribute>(out var attribute))
|
||||||
|
{
|
||||||
|
MethodInfo? showEditorMethod = type.GetMethod("ShowEditor", BindingFlags.Static | BindingFlags.Public,
|
||||||
|
null,
|
||||||
|
new []{ typeof(object), typeof(Type), typeof(string) }, null);
|
||||||
|
|
||||||
|
if (showEditorMethod == null)
|
||||||
|
{
|
||||||
|
Log.Error($"No ShowEditor-method found for type {type}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ShowCustomEditorMethod method = showEditorMethod.GetDelegate<ShowCustomEditorMethod>();
|
||||||
|
|
||||||
|
_customEditors.Add(attribute.Type, method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryShowCustomEditor(object reference, Type fieldType, string fieldName, out object? editedValue)
|
||||||
|
{
|
||||||
|
editedValue = DidNotChange;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Try to match the concrete type first (e.g. Foo -> Foo and Foo<Bar> -> List<Bar>)
|
||||||
|
// Note: Foo<Bar> wont match a serializer for Foo<>
|
||||||
|
if (_customEditors.TryGetValue(fieldType, out ShowCustomEditorMethod customEditorMethod))
|
||||||
|
{
|
||||||
|
editedValue = customEditorMethod(reference, fieldType, fieldName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldType.IsGenericType && _customEditors.TryGetValue(fieldType.GetGenericTypeDefinition(), out customEditorMethod))
|
||||||
|
{
|
||||||
|
editedValue = customEditorMethod(reference, fieldType, fieldName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public static bool ShowFieldInEditor(FieldInfo fieldInfo)
|
public static bool ShowFieldInEditor(FieldInfo fieldInfo)
|
||||||
{
|
{
|
||||||
@@ -49,12 +106,12 @@ internal class EntityEditor
|
|||||||
ShowEditor(entityType, instance);
|
ShowEditor(entityType, instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static T GetAttribute<T>(IEnumerable<Attribute> attributes) where T : Attribute
|
private static T? GetAttribute<T>(IEnumerable<Attribute>? attributes) where T : Attribute
|
||||||
{
|
{
|
||||||
return (T)attributes?.FirstOrDefault(a => a is T);
|
return (T?)attributes?.FirstOrDefault(a => a is T);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static object ShowPrimitiveEditor(object reference, Type fieldType, string fieldName, IEnumerable<Attribute> attributes)
|
private static object ShowPrimitiveEditor(object reference, Type fieldType, string fieldName, IEnumerable<Attribute>? attributes)
|
||||||
{
|
{
|
||||||
object newValue = DidNotChange;
|
object newValue = DidNotChange;
|
||||||
|
|
||||||
@@ -75,7 +132,7 @@ internal class EntityEditor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RangeAttribute range = GetAttribute<RangeAttribute>(attributes);
|
RangeAttribute? range = GetAttribute<RangeAttribute>(attributes);
|
||||||
|
|
||||||
// Get Min and Max Values from Type T
|
// Get Min and Max Values from Type T
|
||||||
T min = ReadStaticField<T>("MinValue");
|
T min = ReadStaticField<T>("MinValue");
|
||||||
@@ -91,8 +148,8 @@ internal class EntityEditor
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MinimumAttribute minimum = GetAttribute<MinimumAttribute>(attributes);
|
MinimumAttribute? minimum = GetAttribute<MinimumAttribute>(attributes);
|
||||||
MaximumAttribute maximum = GetAttribute<MaximumAttribute>(attributes);
|
MaximumAttribute? maximum = GetAttribute<MaximumAttribute>(attributes);
|
||||||
|
|
||||||
// Don't use range, if it is invalid
|
// Don't use range, if it is invalid
|
||||||
if (minimum != null && maximum != null && minimum.Min > maximum.Max)
|
if (minimum != null && maximum != null && minimum.Min > maximum.Max)
|
||||||
@@ -197,24 +254,24 @@ internal class EntityEditor
|
|||||||
return newValue;
|
return newValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static object ShowFieldEditor(object reference, Type fieldType, string fieldName, IEnumerable<Attribute> attributes = null)
|
public static object ShowFieldEditor(object reference, Type fieldType, string fieldName, IEnumerable<Attribute>? attributes = null)
|
||||||
{
|
{
|
||||||
ReadonlyAttribute readonlyAttribute = GetAttribute<ReadonlyAttribute>(attributes);
|
ReadonlyAttribute readonlyAttribute = GetAttribute<ReadonlyAttribute>(attributes);
|
||||||
|
|
||||||
ImGui.BeginDisabled(readonlyAttribute != null);
|
ImGui.BeginDisabled(readonlyAttribute != null);
|
||||||
|
|
||||||
object newValue = DidNotChange;
|
object? newValue = DidNotChange;
|
||||||
|
|
||||||
if (fieldType.IsGenericType)
|
if (TryShowCustomEditor(reference, fieldType, fieldName, out newValue))
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (fieldType.IsGenericType)
|
||||||
{
|
{
|
||||||
if (fieldType.GetGenericTypeDefinition() == typeof(List<>))
|
if (fieldType.GetGenericTypeDefinition() == typeof(List<>))
|
||||||
{
|
{
|
||||||
newValue = ShowListEditor(fieldType, reference, fieldName);
|
newValue = ShowListEditor(fieldType, reference, fieldName);
|
||||||
}
|
}
|
||||||
else if (fieldType.GetGenericTypeDefinition() == typeof(Dictionary<,>))
|
|
||||||
{
|
|
||||||
ImGui.Text($"{fieldName} Dictionary");
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// TODO: Try to use a custom editor
|
// TODO: Try to use a custom editor
|
||||||
|
|||||||
Reference in New Issue
Block a user