mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 13:01:52 +00:00
Scripts: More range attributes for fields
- Added Minimum- and Maximum attributes - Documentation for Min, Max and Range - EntityEditor: Refactoring + Range Attributes
This commit is contained in:
@@ -3,6 +3,7 @@ using System.Collections;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using GlitchyEngine.Core;
|
using GlitchyEngine.Core;
|
||||||
@@ -40,73 +41,85 @@ internal class EntityEditor
|
|||||||
ShowEditor(entityType, instance);
|
ShowEditor(entityType, instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static object ShowFieldEditor(object reference, Type fieldType, string fieldName, IEnumerable<Attribute> attributes = null)
|
private static T GetAttribute<T>(IEnumerable<Attribute> attributes) where T : Attribute
|
||||||
{
|
|
||||||
T GetAttribute<T>() where T : Attribute
|
|
||||||
{
|
{
|
||||||
return (T)attributes?.FirstOrDefault(a => a is T);
|
return (T)attributes?.FirstOrDefault(a => a is T);
|
||||||
}
|
}
|
||||||
|
|
||||||
ReadonlyAttribute readonlyAttribute = GetAttribute<ReadonlyAttribute>();
|
private static object ShowPrimitiveEditor(object reference, Type fieldType, string fieldName, IEnumerable<Attribute> attributes)
|
||||||
|
{
|
||||||
ImGui.BeginDisabled(readonlyAttribute != null);
|
|
||||||
|
|
||||||
object newValue = DidNotChange;
|
object newValue = DidNotChange;
|
||||||
|
|
||||||
if (fieldType.IsGenericType)
|
|
||||||
{
|
|
||||||
if (fieldType.GetGenericTypeDefinition() == typeof(List<>))
|
|
||||||
{
|
|
||||||
newValue = ShowListEditor(fieldType, reference, fieldName);
|
|
||||||
}
|
|
||||||
else if (fieldType.GetGenericTypeDefinition() == typeof(Dictionary<,>))
|
|
||||||
{
|
|
||||||
ImGui.Text($"{fieldName} Dictionary");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (fieldType.IsArray)
|
|
||||||
{
|
|
||||||
ImGui.Text($"{fieldName} Array");
|
|
||||||
}
|
|
||||||
else if (fieldType.IsEnum)
|
|
||||||
{
|
|
||||||
if (ImGui.BeginCombo(fieldName, reference.ToString()))
|
|
||||||
{
|
|
||||||
foreach (object enumValue in Enum.GetValues(fieldType))
|
|
||||||
{
|
|
||||||
if (ImGui.Selectable(enumValue.ToString(), enumValue == reference))
|
|
||||||
{
|
|
||||||
newValue = enumValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndCombo();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (fieldType.IsPrimitive)
|
|
||||||
{
|
|
||||||
ImGui.Text($"{fieldName} Primitive");
|
|
||||||
|
|
||||||
unsafe void DragScalar<T>(ImGuiDataType dataType) where T : unmanaged
|
unsafe void DragScalar<T>(ImGuiDataType dataType) where T : unmanaged
|
||||||
{
|
{
|
||||||
RangeAttribute range = GetAttribute<RangeAttribute>();
|
T ChangeTypeSafe(object value)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return (T)Convert.ChangeType(value, typeof(T));
|
||||||
|
}
|
||||||
|
catch (OverflowException e)
|
||||||
|
{
|
||||||
|
// Log.Error($"Could not apply value: {e.Message}");
|
||||||
|
ImGui.TextColored(new Vector4(1, 0, 0, 1), $"Value {value} can not be converted to type {typeof(T)}: {e.Message}.");
|
||||||
|
|
||||||
T min = default;
|
return default;
|
||||||
T max = default;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RangeAttribute range = GetAttribute<RangeAttribute>(attributes);
|
||||||
|
|
||||||
|
T min = ReadStaticField<T>("MinValue");
|
||||||
|
T max = ReadStaticField<T>("MaxValue");
|
||||||
|
float speed = 1.0f;
|
||||||
|
|
||||||
|
// RangeAttribute takes precedence over Minimum- and Maximum-Attribute
|
||||||
if (range != null)
|
if (range != null)
|
||||||
{
|
{
|
||||||
min = (T)Convert.ChangeType(range.Min, typeof(T));
|
min = ChangeTypeSafe(range.Min);
|
||||||
max = (T)Convert.ChangeType(range.Max, typeof(T));
|
max = ChangeTypeSafe(range.Max);
|
||||||
|
speed = range.Speed;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MinimumAttribute minimum = GetAttribute<MinimumAttribute>(attributes);
|
||||||
|
MaximumAttribute maximum = GetAttribute<MaximumAttribute>(attributes);
|
||||||
|
|
||||||
|
// Don't use range, if it is invalid
|
||||||
|
if (minimum != null && maximum != null && minimum.Min > maximum.Max)
|
||||||
|
{
|
||||||
|
//Log.Error($"Minimum value ({minimum.Min}) specified for field {fieldName} must not be greater than the maximum value ({maximum.Max})! Ignoring range...");
|
||||||
|
ImGui.TextColored(new Vector4(1, 0, 0, 1), $"Minimum value ({minimum.Min}) specified for field {fieldName} must not be greater than the maximum value ({maximum.Max})! Ignoring range...");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (minimum != null)
|
||||||
|
min = ChangeTypeSafe(minimum.Min);
|
||||||
|
|
||||||
|
if (maximum != null)
|
||||||
|
max = ChangeTypeSafe(maximum.Max);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var value = (T)reference;
|
var value = (T)reference;
|
||||||
if (ImGui.DragScalar(fieldName, dataType, (IntPtr)(&value), 1, (IntPtr)(&min), (IntPtr)(&max)))
|
|
||||||
|
if (range?.Slider == true)
|
||||||
|
{
|
||||||
|
if (ImGui.SliderScalar(fieldName, dataType, (IntPtr)(&value), (IntPtr)(&min), (IntPtr)(&max)))
|
||||||
{
|
{
|
||||||
newValue = value;
|
newValue = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ImGui.DragScalar(fieldName, dataType, (IntPtr)(&value), speed, (IntPtr)(&min), (IntPtr)(&max)))
|
||||||
|
{
|
||||||
|
newValue = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: allow editing in edit-mode
|
||||||
if (reference != null)
|
if (reference != null)
|
||||||
{
|
{
|
||||||
if (fieldType == typeof(bool))
|
if (fieldType == typeof(bool))
|
||||||
@@ -145,8 +158,71 @@ internal class EntityEditor
|
|||||||
DragScalar<float>(ImGuiDataType.Float);
|
DragScalar<float>(ImGuiDataType.Float);
|
||||||
else if (fieldType == typeof(double))
|
else if (fieldType == typeof(double))
|
||||||
DragScalar<double>(ImGuiDataType.Double);
|
DragScalar<double>(ImGuiDataType.Double);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.TextColored(new Vector4(1, 0, 0, 1), $"{fieldName}: Type {fieldType} is not implemented.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object ShowEnumEditor(object reference, Type fieldType, string fieldName)
|
||||||
|
{
|
||||||
|
object newValue = DidNotChange;
|
||||||
|
|
||||||
|
if (ImGui.BeginCombo(fieldName, reference.ToString()))
|
||||||
|
{
|
||||||
|
foreach (object enumValue in Enum.GetValues(fieldType))
|
||||||
|
{
|
||||||
|
if (ImGui.Selectable(enumValue.ToString(), enumValue == reference))
|
||||||
|
{
|
||||||
|
newValue = enumValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndCombo();
|
||||||
|
}
|
||||||
|
|
||||||
|
return newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object ShowFieldEditor(object reference, Type fieldType, string fieldName, IEnumerable<Attribute> attributes = null)
|
||||||
|
{
|
||||||
|
ReadonlyAttribute readonlyAttribute = GetAttribute<ReadonlyAttribute>(attributes);
|
||||||
|
|
||||||
|
ImGui.BeginDisabled(readonlyAttribute != null);
|
||||||
|
|
||||||
|
object newValue = DidNotChange;
|
||||||
|
|
||||||
|
if (fieldType.IsGenericType)
|
||||||
|
{
|
||||||
|
if (fieldType.GetGenericTypeDefinition() == typeof(List<>))
|
||||||
|
{
|
||||||
|
newValue = ShowListEditor(fieldType, reference, fieldName);
|
||||||
|
}
|
||||||
|
else if (fieldType.GetGenericTypeDefinition() == typeof(Dictionary<,>))
|
||||||
|
{
|
||||||
|
ImGui.Text($"{fieldName} Dictionary");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: Try to use a custom editor
|
||||||
|
ImGui.TextColored(new Vector4(1, 0, 0, 1), $"{fieldName}: Type {fieldType} is not implemented.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (fieldType.IsArray)
|
||||||
|
{
|
||||||
|
ImGui.Text($"{fieldName} Array");
|
||||||
|
}
|
||||||
|
else if (fieldType.IsEnum)
|
||||||
|
{
|
||||||
|
newValue = ShowEnumEditor(reference, fieldType, fieldName);
|
||||||
|
}
|
||||||
|
else if (fieldType.IsPrimitive)
|
||||||
|
{
|
||||||
|
newValue = ShowPrimitiveEditor(reference, fieldType, fieldName, attributes);
|
||||||
|
}
|
||||||
else if (fieldType.IsValueType)
|
else if (fieldType.IsValueType)
|
||||||
{
|
{
|
||||||
if (fieldType == typeof(bool2))
|
if (fieldType == typeof(bool2))
|
||||||
@@ -361,4 +437,16 @@ internal class EntityEditor
|
|||||||
if (baseType.IsAssignableFrom(type) && !type.IsAbstract) yield return type;
|
if (baseType.IsAssignableFrom(type) && !type.IsAbstract) yield return type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static T ReadStaticField<T>(string name)
|
||||||
|
{
|
||||||
|
FieldInfo field = typeof(T).GetField(name, BindingFlags.Public | BindingFlags.Static);
|
||||||
|
|
||||||
|
if (field == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Type {typeof(T).Name} has no static field \"{name}\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (T)field.GetValue(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,110 @@ using System;
|
|||||||
|
|
||||||
namespace GlitchyEngine.Editor;
|
namespace GlitchyEngine.Editor;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies a minimum and maximum value that can be set using the editor for the field.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This only affects the editor. The range does <b>not</b> affect scripts changing the value of the field.
|
||||||
|
/// </remarks>
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
public sealed class RangeAttribute : Attribute
|
public sealed class RangeAttribute : Attribute
|
||||||
{
|
{
|
||||||
public double Min { get; set; }
|
/// <summary>
|
||||||
public double Max { get; set; }
|
/// The minimum value that can be assigned to the field using the editor.
|
||||||
|
/// </summary>
|
||||||
|
public double Min { get; private set; }
|
||||||
|
|
||||||
public RangeAttribute(double min, double max)
|
/// <summary>
|
||||||
|
/// The maximum value that can be assigned to the field using the editor.
|
||||||
|
/// </summary>
|
||||||
|
public double Max { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The speed with which the value will be changed when dragging in the editor.
|
||||||
|
/// </summary>
|
||||||
|
public float Speed { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If set to <see langword="true"/>, the editor field will be a slider instead of a number field.
|
||||||
|
/// </summary>
|
||||||
|
public bool Slider { get; private set; }
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="RangeAttribute"/> with a minimum, maximum value and optionally a speed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="min"><inheritdoc cref="Min"/></param>
|
||||||
|
/// <param name="max"><inheritdoc cref="Max"/></param>
|
||||||
|
/// <param name="speed"><inheritdoc cref="Speed"/></param>
|
||||||
|
public RangeAttribute(double min, double max, float speed = 1.0f, bool slider = false)
|
||||||
{
|
{
|
||||||
if (max < min)
|
if (max < min)
|
||||||
throw new ArgumentOutOfRangeException(nameof(max), $"{nameof(max)} must be larger than {nameof(min)}.");
|
throw new ArgumentOutOfRangeException(nameof(max), $"{nameof(max)} must be larger than {nameof(min)}.");
|
||||||
|
|
||||||
|
if (speed <= 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(speed), $"{nameof(speed)} must be larger than zero.");
|
||||||
|
|
||||||
Min = min;
|
Min = min;
|
||||||
Max = max;
|
Max = max;
|
||||||
|
Speed = speed;
|
||||||
|
Slider = slider;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies a minimum value that can be set using the editor for the field.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If the field also has a <see cref="RangeAttribute"/>, the <see cref="RangeAttribute"/> takes precedence.
|
||||||
|
/// <br/><br/>
|
||||||
|
/// If the field also has a <see cref="MaximumAttribute"/>, and <see cref="Min"/> is larger than <see cref="MaximumAttribute.Max"/>, then no range will be applied.
|
||||||
|
/// <br/><br/>
|
||||||
|
/// The value specified only affects the editor. It does <b>not</b> affect scripts changing the value of the field.
|
||||||
|
/// </remarks>
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public sealed class MinimumAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The minimum value that can be assigned to the field using the editor.
|
||||||
|
/// </summary>
|
||||||
|
public double Min { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="RangeAttribute"/> with a minimum value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="min"><inheritdoc cref="Min"/></param>
|
||||||
|
public MinimumAttribute(double min)
|
||||||
|
{
|
||||||
|
Min = min;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies a maximum value that can be set using the editor for the field.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If the field also has a <see cref="RangeAttribute"/>, the <see cref="RangeAttribute"/> takes precedence.
|
||||||
|
/// <br/><br/>
|
||||||
|
/// If the field also has a <see cref="MinimumAttribute"/>, and <see cref="MinimumAttribute.Min"/> is larger than <see cref="Max"/>, then no range will be applied.
|
||||||
|
/// <br/><br/>
|
||||||
|
/// The value specified only affects the editor. It does <b>not</b> affect scripts changing the value of the field.
|
||||||
|
/// </remarks>
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public sealed class MaximumAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum value that can be assigned to the field using the editor.
|
||||||
|
/// </summary>
|
||||||
|
public double Max { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="RangeAttribute"/> with a maximum value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="max"><inheritdoc cref="Max"/></param>
|
||||||
|
public MaximumAttribute(double max)
|
||||||
|
{
|
||||||
|
Max = max;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user