From 4830fd289ee304b94e4280c47f20c10aebd2505c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20L=C3=BCbe=C3=9F?= Date: Tue, 30 Jan 2024 23:40:58 +0100 Subject: [PATCH] ComponentWindow finalized ScriptCore: - ActivatorExtension: Fixed creation of default value or null class - ImGuiExtension: added DragUIntX / DoubleX and CheckboxX --- .../src/EditWindows/ComponentEditWindow.bf | 33 +- GlitchyEngine/src/ImGui/ImGuiExtension.bf | 19 +- ScriptCore/Editor/DictionaryEditor.cs | 1 + ScriptCore/Editor/EntityEditor.cs | 354 +++++++++++------- ScriptCore/Extensions/ActivatorExtension.cs | 9 + ScriptCore/Extensions/ImGuiExtension.cs | 51 +++ 6 files changed, 316 insertions(+), 151 deletions(-) diff --git a/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf b/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf index c85a9a8..a45b1b5 100644 --- a/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf +++ b/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf @@ -142,15 +142,7 @@ namespace GlitchyEditor.EditWindows TComponent* component = entity.GetComponent(); - //ImGui.PushID(header); - - /*ImGui.PushClipRect(.(), .(float.MaxValue, float.MaxValue), false); - ImGui.TableNextRow(); - ImGui.TableSetColumnIndex(0); - ImGui.PopClipRect();*/ - bool nodeOpen = ImGui.CollapsingHeader(header.CStr(), .DefaultOpen | .AllowOverlap | .Framed | .SpanFullWidth); - //ImGui.TreeNodeEx(header.CStr(), .DefaultOpen | .AllowOverlap | .Framed | .SpanFullWidth | .SpanAllColumns); if (showComponentContextMenu != null) { @@ -169,20 +161,13 @@ namespace GlitchyEditor.EditWindows } } - if (nodeOpen) + if (nodeOpen && ImGui.BeginTableEx("properties", TableId, 2, .SizingStretchSame | .BordersInner | .Resizable)) { - if (ImGui.BeginTableEx("properties", TableId, 2, .SizingStretchSame | .BordersInner | .Resizable)) - { - if (entity.TryGetComponent(let actualComponent)) - showComponentEditor(entity, actualComponent); + if (entity.TryGetComponent(let actualComponent)) + showComponentEditor(entity, actualComponent); - ImGui.EndTable(); - } - - //ImGui.TreePop(); + ImGui.EndTable(); } - - //ImGui.PopID(); } /// Starts a new property by creating a new table row, writing the name in the first column and entering the second column. @@ -191,11 +176,17 @@ namespace GlitchyEditor.EditWindows ImGui.TableNextRow(); ImGui.TableSetColumnIndex(0); + if (ImGui.TableGetRowIndex() == 0) + ImGui.PushItemWidth(-1); + ImGui.TextUnformatted(propertyName); ImGui.AttachTooltip(propertyName); ImGui.TableSetColumnIndex(1); + + if (ImGui.TableGetRowIndex() == 0) + ImGui.PushItemWidth(-1); } private static void ShowNameComponentEditor(Entity entity) @@ -221,8 +212,12 @@ namespace GlitchyEditor.EditWindows // Copy name to buffer Internal.MemCpy(&nameBuffer, name.Ptr, Math.Min(nameBuffer.Count, name.Length)); + ImGui.PushItemWidth(-1); + StartNewProperty("Name"); + ImGui.PushItemWidth(-1); + if(ImGui.InputText("##Name", &nameBuffer, nameBuffer.Count, .EnterReturnsTrue)) { if(component == null) diff --git a/GlitchyEngine/src/ImGui/ImGuiExtension.bf b/GlitchyEngine/src/ImGui/ImGuiExtension.bf index a665915..56b290e 100644 --- a/GlitchyEngine/src/ImGui/ImGuiExtension.bf +++ b/GlitchyEngine/src/ImGui/ImGuiExtension.bf @@ -284,11 +284,17 @@ namespace ImGui PushID(label); defer PopID(); - PushMultiItemsWidths(NumComponents, CalcItemWidth()); + //PushMultiItemsWidths(NumComponents, CalcItemWidth()); + + float totalWidth = CalcItemWidth(); + + float componentWidth = totalWidth / NumComponents; float lineHeight = GetFont().FontSize + GetStyle().FramePadding.y * 2.0f; ImGui.Vec2 buttonSize = .(lineHeight + 3.0f, lineHeight); + float dragFloatWidth = componentWidth - buttonSize.x - GetStyle().FramePadding.x; + componentLoop: for (int i < NumComponents) { if (i > 0) @@ -300,7 +306,9 @@ namespace ImGui PushStyleColor(.ButtonHovered, VectorButtonColors[i].Hovered.ImGuiU32); PushStyleColor(.ButtonActive, VectorButtonColors[i].Active.ImGuiU32); - ImGui.BeginDisabled(!componentEnabled[i]); + BeginDisabled(!componentEnabled[i]); + + //PushItemWidth(buttonSize.x); if (Button(componentNames[i], buttonSize)) { @@ -325,16 +333,19 @@ namespace ImGui format = numberFormat[0].ToScopeCStr!:componentLoop(); } + PushItemWidth(dragFloatWidth); + if (DragFloat(componentIds[i], &value[i], dragSpeed, minValue[i], maxValue[i], format)) { changed = true; } + + PopItemWidth(); PopStyleVar(); - ImGui.EndDisabled(); + EndDisabled(); - PopItemWidth(); PopStyleColor(3); } diff --git a/ScriptCore/Editor/DictionaryEditor.cs b/ScriptCore/Editor/DictionaryEditor.cs index 460945f..40d49cc 100644 --- a/ScriptCore/Editor/DictionaryEditor.cs +++ b/ScriptCore/Editor/DictionaryEditor.cs @@ -60,6 +60,7 @@ public class DictionaryEditor _dictionaryForNewValue = dictionary; _newDictionaryValue = new DictionaryEntry(); + _newDictionaryValue.Value = ActivatorExtension.CreateDefaultValue(valueType); } ImGuiExtension.AttachTooltip("Add a new Entry to the dictionary."); diff --git a/ScriptCore/Editor/EntityEditor.cs b/ScriptCore/Editor/EntityEditor.cs index 21d3968..d57927e 100644 --- a/ScriptCore/Editor/EntityEditor.cs +++ b/ScriptCore/Editor/EntityEditor.cs @@ -13,6 +13,7 @@ using System.Text; using GlitchyEngine.Core; using GlitchyEngine.Extensions; using GlitchyEngine.Math; +using GlitchyEngine.Math.Attributes; using ImGuiNET; using Component = GlitchyEngine.Core.Component; @@ -66,13 +67,6 @@ internal class EntityEditor ImGui.TableSetColumnIndex(0); } - public static void EndTable() - { - ImGui.EndTable(); - - ImGui.GetItemID(); - } - public static bool StartNewProperty_NewRow = true; /// @@ -184,8 +178,10 @@ internal class EntityEditor return DidNotChange; } - private static object? ShowPrimitiveEditor(object? reference, Type fieldType, string fieldName, IEnumerable? attributes) + private static object ShowPrimitiveEditor(object reference, Type fieldType, string fieldName, IEnumerable? attributes) { + Debug.Assert(reference != null); + string fieldId = StartNewProperty(fieldName); object newValue = DidNotChange; @@ -260,121 +256,114 @@ internal class EntityEditor } } - if (reference != null) + if (fieldType == typeof(bool)) { - if (fieldType == typeof(bool)) + bool value = (bool)reference; + if (ImGui.Checkbox(fieldId, ref value)) + newValue = value; + } + else if (fieldType == typeof(char)) + { + unsafe { - bool value = (bool)reference; - if (ImGui.Checkbox(fieldId, ref value)) - newValue = value; - } - else if (fieldType == typeof(char)) - { - unsafe + // TODO: Allow escaped unicode chars \u XXXX + // TODO: Add text input validation, so that it isn't possible to type invalid chars + + char* value = stackalloc char[4]; + switch((char)reference) { - // TODO: Allow escaped unicode chars \u XXXX - // TODO: Add text input validation, so that it isn't possible to type invalid chars + case '\a': + value[0] = '\\'; + value[1] = 'a'; + break; + case '\b': + value[0] = '\\'; + value[1] = 'b'; + break; + case '\f': + value[0] = '\\'; + value[1] = 'f'; + break; + case '\n': + value[0] = '\\'; + value[1] = 'n'; + break; + case '\r': + value[0] = '\\'; + value[1] = 'r'; + break; + case '\t': + value[0] = '\\'; + value[1] = 't'; + break; + case '\v': + value[0] = '\\'; + value[1] = 'v'; + break; + default: + value[0] = (char)reference; + value[1] = '\0'; + break; + }; - char* value = stackalloc char[4]; - switch((char)reference) + // Enough space to store two code points + byte* buffer = stackalloc byte[8]; + int encodedBytes = Encoding.UTF8.GetBytes(value, 2, buffer, 8); + + // Place string delimiter after encoded UTF8 sequence. + buffer[encodedBytes] = (byte)'\0'; + + if (ImGui.InputText(fieldId, (IntPtr)buffer, 8)) + { + if (buffer[0] == '\\' && buffer[1] != '\0') { - case '\a': - value[0] = '\\'; - value[1] = 'a'; - break; - case '\b': - value[0] = '\\'; - value[1] = 'b'; - break; - case '\f': - value[0] = '\\'; - value[1] = 'f'; - break; - case '\n': - value[0] = '\\'; - value[1] = 'n'; - break; - case '\r': - value[0] = '\\'; - value[1] = 'r'; - break; - case '\t': - value[0] = '\\'; - value[1] = 't'; - break; - case '\v': - value[0] = '\\'; - value[1] = 'v'; - break; - default: - value[0] = (char)reference; - value[1] = '\0'; - break; - }; - - // Enough space to store two code points - byte* buffer = stackalloc byte[8]; - int encodedBytes = Encoding.UTF8.GetBytes(value, 2, buffer, 8); - - // Place string delimiter after encoded UTF8 sequence. - buffer[encodedBytes] = (byte)'\0'; - - if (ImGui.InputText(fieldId, (IntPtr)buffer, 8)) + newValue = (char)buffer[1] switch + { + 'a' => newValue = '\a', + 'b' => newValue = '\b', + 'f' => newValue = '\f', + 'n' => newValue = '\n', + 'r' => newValue = '\r', + 't' => newValue = '\t', + 'v' => newValue = '\v', + _ => newValue = '\\' + }; + } + else { - if (buffer[0] == '\\' && buffer[1] != '\0') - { - newValue = (char)buffer[1] switch - { - 'a' => newValue = '\a', - 'b' => newValue = '\b', - 'f' => newValue = '\f', - 'n' => newValue = '\n', - 'r' => newValue = '\r', - 't' => newValue = '\t', - 'v' => newValue = '\v', - _ => newValue = '\\' - }; - } - else - { - // Only decode first codepoint - Encoding.UTF8.GetChars(buffer, 4, value, 4); + // Only decode first codepoint + Encoding.UTF8.GetChars(buffer, 4, value, 4); - newValue = value[0]; - } - } - } - } - else if (fieldType == typeof(byte)) - DragScalar(ImGuiDataType.U8); - else if (fieldType == typeof(sbyte)) - DragScalar(ImGuiDataType.S8); - else if (fieldType == typeof(short)) - DragScalar(ImGuiDataType.S16); - else if (fieldType == typeof(ushort)) - DragScalar(ImGuiDataType.U16); - else if (fieldType == typeof(int)) - DragScalar(ImGuiDataType.S32); - else if (fieldType == typeof(uint)) - DragScalar(ImGuiDataType.U32); - else if (fieldType == typeof(long)) - DragScalar(ImGuiDataType.S64); - else if (fieldType == typeof(ulong)) - DragScalar(ImGuiDataType.U64); - else if (fieldType == typeof(float)) - DragScalar(ImGuiDataType.Float); - else if (fieldType == typeof(double)) - DragScalar(ImGuiDataType.Double); - else - { - ImGui.TextColored(new Vector4(1, 0, 0, 1), $"{fieldName}: Type {fieldType} is not implemented."); + newValue = value[0]; + } + } } } + else if (fieldType == typeof(byte)) + DragScalar(ImGuiDataType.U8); + else if (fieldType == typeof(sbyte)) + DragScalar(ImGuiDataType.S8); + else if (fieldType == typeof(short)) + DragScalar(ImGuiDataType.S16); + else if (fieldType == typeof(ushort)) + DragScalar(ImGuiDataType.U16); + else if (fieldType == typeof(int)) + DragScalar(ImGuiDataType.S32); + else if (fieldType == typeof(uint)) + DragScalar(ImGuiDataType.U32); + else if (fieldType == typeof(long)) + DragScalar(ImGuiDataType.S64); + else if (fieldType == typeof(ulong)) + DragScalar(ImGuiDataType.U64); + else if (fieldType == typeof(float)) + DragScalar(ImGuiDataType.Float); + else if (fieldType == typeof(double)) + DragScalar(ImGuiDataType.Double); else { - return ActivatorExtension.CreateInstanceSafe(fieldType); + ImGui.TextColored(new Vector4(1, 0, 0, 1), $"{fieldName}: Type {fieldType} is not implemented."); } - + return newValue; } @@ -434,7 +423,7 @@ internal class EntityEditor } else if (fieldType.IsPrimitive) { - newValue = ShowPrimitiveEditor(reference, fieldType, fieldName, attributes); + newValue = ShowPrimitiveEditor(reference!, fieldType, fieldName, attributes); } else if (fieldType.IsValueType) { @@ -442,22 +431,18 @@ internal class EntityEditor { newValue = ShowDecimalEditor(reference, fieldType, fieldName, attributes); } - else if (fieldType == typeof(bool2)) + else if (fieldType == typeof(ColorRGBA)) { - // TODO: The bool vector editor should not be here... - string fieldId = StartNewProperty(fieldName); - bool2 value = (bool2)reference!; - - if (ImGui.Checkbox($"##{fieldName}X", ref value.X)) - newValue = value; - - ImGui.SameLine(); - - if (ImGui.Checkbox($"##{fieldName}Y", ref value.Y)) + ColorRGBA value = (ColorRGBA)reference!; + if (ImGui.ColorEdit4(fieldId, ref Unsafe.As(ref value))) newValue = value; } + else if (fieldType.TryGetCustomAttribute(out VectorAttribute vectorAttribute)) + { + newValue = ShowVectorEditor(reference, fieldType, fieldName, vectorAttribute); + } else { newValue = ShowObjectEditor(reference, fieldType, fieldName, attributes); @@ -485,6 +470,121 @@ internal class EntityEditor return newValue; } + private static object ShowVectorEditor(object? reference, Type fieldType, string fieldName, VectorAttribute vectorAttribute) + { + object newValue = DidNotChange; + + string fieldId = StartNewProperty(fieldName); + + if (vectorAttribute.Type == typeof(bool)) + { + if (vectorAttribute.ComponentCount == 2) + { + bool2 value = (bool2)reference!; + if (ImGuiExtension.Checkbox2(fieldId, ref value)) + newValue = value; + } + else if (vectorAttribute.ComponentCount == 3) + { + bool3 value = (bool3)reference!; + if (ImGuiExtension.Checkbox3(fieldId, ref value)) + newValue = value; + } + else if (vectorAttribute.ComponentCount == 4) + { + bool4 value = (bool4)reference!; + if (ImGuiExtension.Checkbox4(fieldId, ref value)) + newValue = value; + } + } + else if (vectorAttribute.Type == typeof(int)) + { + if (vectorAttribute.ComponentCount == 2) + { + int2 value = (int2)reference!; + if (ImGui.DragInt2(fieldId, ref value.X)) + newValue = value; + } + else if (vectorAttribute.ComponentCount == 3) + { + int3 value = (int3)reference!; + if (ImGui.DragInt3(fieldId, ref value.X)) + newValue = value; + } + else if (vectorAttribute.ComponentCount == 4) + { + int4 value = (int4)reference!; + if (ImGui.DragInt4(fieldId, ref value.X)) + newValue = value; + } + } + else if (vectorAttribute.Type == typeof(uint)) + { + if (vectorAttribute.ComponentCount == 2) + { + uint2 value = (uint2)reference!; + if (ImGuiExtension.DragUInt2(fieldId, ref value)) + newValue = value; + } + else if (vectorAttribute.ComponentCount == 3) + { + uint3 value = (uint3)reference!; + if (ImGuiExtension.DragUInt3(fieldId, ref value)) + newValue = value; + } + else if (vectorAttribute.ComponentCount == 4) + { + uint4 value = (uint4)reference!; + if (ImGuiExtension.DragUInt4(fieldId, ref value)) + newValue = value; + } + } + else if (vectorAttribute.Type == typeof(float)) + { + if (vectorAttribute.ComponentCount == 2) + { + float2 value = (float2)reference!; + if (ImGui.DragFloat2(fieldId, ref Unsafe.As(ref value))) + newValue = value; + } + else if (vectorAttribute.ComponentCount == 3) + { + float3 value = (float3)reference!; + if (ImGui.DragFloat3(fieldId, ref Unsafe.As(ref value))) + newValue = value; + } + else if (vectorAttribute.ComponentCount == 4) + { + float4 value = (float4)reference!; + if (ImGui.DragFloat4(fieldId, ref Unsafe.As(ref value))) + newValue = value; + } + } + else if (vectorAttribute.Type == typeof(double)) + { + if (vectorAttribute.ComponentCount == 2) + { + double2 value = (double2)reference!; + if (ImGuiExtension.DragDouble2(fieldId, ref value)) + newValue = value; + } + else if (vectorAttribute.ComponentCount == 3) + { + double3 value = (double3)reference!; + if (ImGuiExtension.DragDouble3(fieldId, ref value)) + newValue = value; + } + else if (vectorAttribute.ComponentCount == 4) + { + double4 value = (double4)reference!; + if (ImGuiExtension.DragDouble4(fieldId, ref value)) + newValue = value; + } + } + + return newValue; + } + private static object? ShowObjectEditor(object? reference, Type fieldType, string fieldName, IEnumerable attributes) { object? newValue = DidNotChange; @@ -846,9 +946,7 @@ internal class EntityEditor try { - // Reference values can be null - object? newElement = elementType.IsByRef ? null : ActivatorExtension.CreateInstanceSafe(elementType); - + object? newElement = ActivatorExtension.CreateDefaultValue(elementType); list?.Add(newElement); } catch (Exception ex) @@ -912,8 +1010,8 @@ internal class EntityEditor newList = newArray; } - - object newElement = Activator.CreateInstance(elementType); + + object? newElement = ActivatorExtension.CreateDefaultValue(elementType!); ((IList)newList)[newIndex] = newElement; } diff --git a/ScriptCore/Extensions/ActivatorExtension.cs b/ScriptCore/Extensions/ActivatorExtension.cs index 9dd2df8..9a289b5 100644 --- a/ScriptCore/Extensions/ActivatorExtension.cs +++ b/ScriptCore/Extensions/ActivatorExtension.cs @@ -66,4 +66,13 @@ public static class ActivatorExtension return component; } + + /// + /// Returns the null for reference types and an initialized value for structs / value types. + /// + /// The type to create the default value for. + public static object? CreateDefaultValue(Type type) + { + return type.IsClass ? null : CreateInstanceSafe(type); + } } diff --git a/ScriptCore/Extensions/ImGuiExtension.cs b/ScriptCore/Extensions/ImGuiExtension.cs index b799e7b..6889206 100644 --- a/ScriptCore/Extensions/ImGuiExtension.cs +++ b/ScriptCore/Extensions/ImGuiExtension.cs @@ -1,4 +1,6 @@ using System; +using System.Runtime.CompilerServices; +using GlitchyEngine.Math; using ImGuiNET; namespace GlitchyEngine.Extensions; @@ -71,4 +73,53 @@ public static class ImGuiExtension { ScriptGlue.ImGuiExtension_ListElementGrabber(); } + + public static bool Checkbox2(string label, ref bool2 value) => CheckboxN(2, label, ref value.X); + public static bool Checkbox3(string label, ref bool3 value) => CheckboxN(3, label, ref value.X); + public static bool Checkbox4(string label, ref bool4 value) => CheckboxN(4, label, ref value.X); + + public static bool CheckboxN(int componentCount, string label, ref bool value) + { + bool changed = false; + + for (int i = 0; i < componentCount; i++) + { + if (i != 0) + ImGui.SameLine(); + + changed |= ImGui.Checkbox($"##{label}{i}", ref Unsafe.Add(ref value, i)); + + } + return changed; + } + + public static unsafe bool DragUInt2(string label, ref uint2 value) + { + return ImGui.DragScalarN(label, ImGuiDataType.U32, (IntPtr)Unsafe.AsPointer(ref value), 2); + } + + public static unsafe bool DragUInt3(string label, ref uint3 value) + { + return ImGui.DragScalarN(label, ImGuiDataType.U32, (IntPtr)Unsafe.AsPointer(ref value), 3); + } + + public static unsafe bool DragUInt4(string label, ref uint4 value) + { + return ImGui.DragScalarN(label, ImGuiDataType.U32, (IntPtr)Unsafe.AsPointer(ref value), 4); + } + + public static unsafe bool DragDouble2(string label, ref double2 value) + { + return ImGui.DragScalarN(label, ImGuiDataType.Double, (IntPtr)Unsafe.AsPointer(ref value), 2); + } + + public static unsafe bool DragDouble3(string label, ref double3 value) + { + return ImGui.DragScalarN(label, ImGuiDataType.Double, (IntPtr)Unsafe.AsPointer(ref value), 3); + } + + public static unsafe bool DragDouble4(string label, ref double4 value) + { + return ImGui.DragScalarN(label, ImGuiDataType.Double, (IntPtr)Unsafe.AsPointer(ref value), 4); + } }