diff --git a/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf b/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf index d8a1f11..c85a9a8 100644 --- a/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf +++ b/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf @@ -37,7 +37,7 @@ namespace GlitchyEditor.EditWindows protected override void InternalShow() { - ImGui.PushStyleVar(.WindowMinSize, ImGui.Vec2(1000, 100)); + ImGui.SetNextWindowSizeConstraints(.(200, 200), .(-1, -1)); if(!ImGui.Begin(s_WindowTitle, &_open, .None)) { @@ -56,16 +56,29 @@ namespace GlitchyEditor.EditWindows _editVerticesPolygonCollider2D = false; } - ImGui.PopStyleVar(); ImGui.End(); } + private static uint32 TableId; + private void ShowComponents(Entity entity) { - ShowNameComponentEditor(entity); + float cellPaddingY = ImGui.GetTextLineHeight() / 3.0f; + + ImGui.PushStyleVar(.CellPadding, ImGui.Vec2(ImGui.GetStyle().CellPadding.x, cellPaddingY)); + + TableId = ImGui.GetID("properties"); + + if (ImGui.BeginTableEx("properties", TableId, 2, .SizingStretchSame | .BordersInner | .Resizable)) + { + ShowNameComponentEditor(entity); + + ImGui.EndTable(); + } ShowComponentEditor("Transform", entity, => ShowTransformComponentEditor); ShowComponentEditor("Camera", entity, => ShowCameraComponentEditor, => ShowComponentContextMenu); + ShowComponentEditor("Sprite Renderer", entity, => ShowSpriteRendererComponentEditor, => ShowComponentContextMenu); ShowComponentEditor("Circle Renderer", entity, => ShowCircleRendererComponentEditor, => ShowComponentContextMenu); ShowComponentEditor("Mesh Renderer", entity, => ShowMeshRendererComponentEditor, => ShowComponentContextMenu); @@ -76,6 +89,8 @@ namespace GlitchyEditor.EditWindows ShowComponentEditor("Circle collider 2D", entity, => ShowCircleCollider2DComponentEditor, => ShowComponentContextMenu); ShowComponentEditor("Polygon collider 2D", entity, => ShowPolygonCollider2DComponentEditor, => ShowComponentContextMenu); ShowComponentEditor("Script Component", entity, => ShowScriptComponentEditor, => ShowComponentContextMenu); + + ImGui.PopStyleVar(); ShowAddComponentButton(entity); } @@ -127,9 +142,15 @@ namespace GlitchyEditor.EditWindows TComponent* component = entity.GetComponent(); - ImGui.PushID(header); + //ImGui.PushID(header); - bool nodeOpen = ImGui.TreeNodeEx(header.CStr(), .DefaultOpen | .AllowOverlap | .Framed | .SpanFullWidth); + /*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) { @@ -150,13 +171,31 @@ namespace GlitchyEditor.EditWindows if (nodeOpen) { - if (entity.TryGetComponent(let actualComponent)) - showComponentEditor(entity, actualComponent); + if (ImGui.BeginTableEx("properties", TableId, 2, .SizingStretchSame | .BordersInner | .Resizable)) + { + if (entity.TryGetComponent(let actualComponent)) + showComponentEditor(entity, actualComponent); - ImGui.TreePop(); + ImGui.EndTable(); + } + + //ImGui.TreePop(); } - ImGui.PopID(); + //ImGui.PopID(); + } + + /// Starts a new property by creating a new table row, writing the name in the first column and entering the second column. + private static void StartNewProperty(StringView propertyName) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + + ImGui.TextUnformatted(propertyName); + + ImGui.AttachTooltip(propertyName); + + ImGui.TableSetColumnIndex(1); } private static void ShowNameComponentEditor(Entity entity) @@ -182,7 +221,9 @@ namespace GlitchyEditor.EditWindows // Copy name to buffer Internal.MemCpy(&nameBuffer, name.Ptr, Math.Min(nameBuffer.Count, name.Length)); - if(ImGui.InputText("Name", &nameBuffer, nameBuffer.Count)) + StartNewProperty("Name"); + + if(ImGui.InputText("##Name", &nameBuffer, nameBuffer.Count, .EnterReturnsTrue)) { if(component == null) { @@ -202,7 +243,9 @@ namespace GlitchyEditor.EditWindows textWidth += ImGui.GetStyle().FramePadding.x * 3.0f; float3 position = transform.Position; - if (ImGui.EditFloat3("Position", ref position, .Zero, 0.1f, textWidth)) + + StartNewProperty("Position"); + if (ImGui.Float3Editor("##Position", ref position, resetValues: .Zero, dragSpeed: 0.1f)) { transform.Position = position; @@ -219,11 +262,13 @@ namespace GlitchyEditor.EditWindows if (entity.HasComponent()) { + // Disable X and Y rotation for 2D rigid bodies componentEditable.XY = false; rotationEuler.XY = 0; } - - if (ImGui.EditFloat3("Rotation", ref rotationEuler, .Zero, 0.1f, textWidth, componentEnabled: componentEditable)) + + StartNewProperty("Rotation"); + if (ImGui.Float3Editor("##Rotation", ref rotationEuler, resetValues: .Zero, dragSpeed: 0.1f, componentEnabled: componentEditable, format: .("%.3f°",))) { transform.EditorRotationEuler = MathHelper.ToRadians(rotationEuler); @@ -235,7 +280,9 @@ namespace GlitchyEditor.EditWindows } float3 scale = transform.Scale; - if (ImGui.EditFloat3("Scale", ref scale, .One, 0.1f, textWidth)) + + StartNewProperty("Scale"); + if (ImGui.Float3Editor("##Scale", ref scale, resetValues: .One, dragSpeed: 0.1f)) transform.Scale = scale; } @@ -243,13 +290,15 @@ namespace GlitchyEditor.EditWindows private static void ShowCameraComponentEditor(Entity entity, CameraComponent* cameraComponent) { - ImGui.Checkbox("Primary", &cameraComponent.Primary); + StartNewProperty("Is Primary"); + ImGui.Checkbox("##Is_Primary", &cameraComponent.Primary); var camera = ref cameraComponent.Camera; String typeName = strings[camera.ProjectionType.Underlying]; - - if (ImGui.BeginCombo("Projection", typeName.CStr())) + + StartNewProperty("Projection"); + if (ImGui.BeginCombo("##Projection", typeName.CStr())) { for (int i = 0; i < 3; i++) { @@ -269,51 +318,61 @@ namespace GlitchyEditor.EditWindows if (camera.ProjectionType == .Perspective) { + StartNewProperty("Fov Y"); float fovY = MathHelper.ToDegrees(camera.PerspectiveFovY); - if (ImGui.DragFloat("Fov Y", &fovY, 0.1f)) + if (ImGui.DragFloat("##Fov Y", &fovY, 0.1f, format: "%.3f°")) camera.PerspectiveFovY = MathHelper.ToRadians(fovY); + StartNewProperty("Near"); float near = camera.PerspectiveNearPlane; - if (ImGui.DragFloat("Near", &near, 0.1f)) + if (ImGui.DragFloat("##Near", &near, 0.1f)) camera.PerspectiveNearPlane = near; - + + StartNewProperty("Far"); float far = camera.PerspectiveFarPlane; - if (ImGui.DragFloat("Far", &far, 0.1f)) + if (ImGui.DragFloat("##Far", &far, 0.1f)) camera.PerspectiveFarPlane = far; } else if (camera.ProjectionType == .InfinitePerspective) { + StartNewProperty("Vertical FOV"); float fovY = MathHelper.ToDegrees(camera.PerspectiveFovY); - if (ImGui.DragFloat("Vertical FOV", &fovY, 0.1f)) + if (ImGui.DragFloat("##Vertical FOV", &fovY, 0.1f, format: "%.3f°")) camera.PerspectiveFovY = MathHelper.ToRadians(fovY); + StartNewProperty("Near"); float near = camera.PerspectiveNearPlane; - if (ImGui.DragFloat("Near", &near, 0.1f)) + if (ImGui.DragFloat("##Near", &near, 0.1f)) camera.PerspectiveNearPlane = near; } else if (camera.ProjectionType == .Orthographic) { + StartNewProperty("Size"); float size = camera.OrthographicHeight; - if (ImGui.DragFloat("Size", &size, 0.1f)) + if (ImGui.DragFloat("##Size", &size, 0.1f)) camera.OrthographicHeight = size; + StartNewProperty("Near"); float near = camera.OrthographicNearPlane; - if (ImGui.DragFloat("Near", &near, 0.1f)) + if (ImGui.DragFloat("##Near", &near, 0.1f)) camera.OrthographicNearPlane = near; - + + StartNewProperty("Far"); float far = camera.OrthographicFarPlane; - if (ImGui.DragFloat("Far", &far, 0.1f)) + if (ImGui.DragFloat("##Far", &far, 0.1f)) camera.OrthographicFarPlane = far; } - + + StartNewProperty("Fixed Aspect Ratio"); bool fixedAspectRatio = camera.FixedAspectRatio; - if (ImGui.Checkbox("Fixed Aspect Ratio", &fixedAspectRatio)) + if (ImGui.Checkbox("##Fixed Aspect Ratio", &fixedAspectRatio)) camera.FixedAspectRatio = fixedAspectRatio; if (fixedAspectRatio) { + StartNewProperty("Aspect Ratio"); float aspect = camera.AspectRatio; - if (ImGui.DragFloat("Aspect Ratio", &aspect, 0.1f)) + if (ImGui.DragFloat("##Aspect Ratio", &aspect, 0.1f)) camera.AspectRatio = aspect; } } @@ -321,10 +380,12 @@ namespace GlitchyEditor.EditWindows private static void ShowSpriteRendererComponentEditor(Entity entity, SpriteRendererComponent* spriteRendererComponent) { ColorRGBA spriteColor = ColorRGBA.LinearToSRGB(spriteRendererComponent.Color); - if (ImGui.ColorEdit4("Color", ref spriteColor)) + StartNewProperty("Color"); + if (ImGui.ColorEdit4("##Color", ref spriteColor)) spriteRendererComponent.Color = ColorRGBA.SRgbToLinear(spriteColor); - - ImGui.Button("Texture"); + + StartNewProperty("Texture"); + ImGui.Button("..."); if (ImGui.BeginDragDropTarget()) { @@ -342,17 +403,19 @@ namespace GlitchyEditor.EditWindows ImGui.EndDragDropTarget(); } - - ImGui.EditVector<4>("UV Transform", ref *(float[4]*)&spriteRendererComponent.UvTransform); + StartNewProperty("UV Transform"); + ImGui.Float4Editor("##UV Transform", ref spriteRendererComponent.UvTransform, resetValues: float4(0, 0, 1, 1)); } private static void ShowCircleRendererComponentEditor(Entity entity, CircleRendererComponent* circleRendererComponent) { ColorRGBA spriteColor = ColorRGBA.LinearToSRGB(circleRendererComponent.Color); - if (ImGui.ColorEdit4("Color", ref spriteColor)) + StartNewProperty("Color"); + if (ImGui.ColorEdit4("##Color", ref spriteColor)) circleRendererComponent.Color = ColorRGBA.SRgbToLinear(spriteColor); - - ImGui.Button("Texture"); + + StartNewProperty("Texture"); + ImGui.Button("..."); if (ImGui.BeginDragDropTarget()) { @@ -370,16 +433,16 @@ namespace GlitchyEditor.EditWindows ImGui.EndDragDropTarget(); } - - ImGui.EditVector<4>("UV Transform", ref *(float[4]*)&circleRendererComponent.UvTransform); - - ImGui.DragFloat("Inner Radius", &circleRendererComponent.InnerRadius, 0.1f, 0.0f, 1.0f); + StartNewProperty("UV Transform"); + ImGui.Float4Editor("##UV Transform", ref circleRendererComponent.UvTransform, resetValues: float4(0, 0, 1, 1)); + + StartNewProperty("Inner Radius"); + ImGui.DragFloat("##Inner Radius", &circleRendererComponent.InnerRadius, 0.1f, 0.0f, 1.0f); } private static void ShowMeshRendererComponentEditor(Entity entity, MeshRendererComponent* meshRendererComponent) { - ImGui.TextUnformatted("Material:"); - ImGui.SameLine(); + StartNewProperty("Material"); Material material = meshRendererComponent.Material; @@ -412,8 +475,9 @@ namespace GlitchyEditor.EditWindows { const String[?] bodyTypeStrings = .("Static", "Dynamic", "Kinematic"); String bodyTypeName = bodyTypeStrings[rigidBodyComponent.BodyType.Underlying]; - - if (ImGui.BeginCombo("Type", bodyTypeName.CStr())) + + StartNewProperty("Type"); + if (ImGui.BeginCombo("##Type", bodyTypeName.CStr())) { for (int i = 0; i < 3; i++) { @@ -432,13 +496,15 @@ namespace GlitchyEditor.EditWindows } bool isFixedRotation = rigidBodyComponent.FixedRotation; - if (ImGui.Checkbox("Fixed Rotation", &isFixedRotation)) + StartNewProperty("Fixed Rotation"); + if (ImGui.Checkbox("##Fixed Rotation", &isFixedRotation)) { rigidBodyComponent.FixedRotation = isFixedRotation; } float gravityScale = rigidBodyComponent.GravityScale; - if (ImGui.DragFloat("Gravity Scale", &gravityScale)) + StartNewProperty("Gravity Scale"); + if (ImGui.DragFloat("##Gravity Scale", &gravityScale)) { rigidBodyComponent.GravityScale = gravityScale; } @@ -446,111 +512,133 @@ namespace GlitchyEditor.EditWindows private static void ShowBoxCollider2DComponentEditor(Entity entity, BoxCollider2DComponent* boxCollider) { - float textWidth = ImGui.CalcTextSize("Offset".CStr()).x; - textWidth += ImGui.GetStyle().FramePadding.x * 3.0f; - - float2 offset = boxCollider.Offset; - if (ImGui.EditFloat2("Offset", ref offset, .Zero, 0.1f, textWidth)) + StartNewProperty("Offset"); + if (ImGui.Float2Editor("##Offset", ref offset, .Zero, 0.1f)) boxCollider.Offset = offset; float2 size = boxCollider.Size; - if (ImGui.EditFloat2("Size", ref size, .Zero, 0.1f, textWidth, float2(0.01f, 0.01f), float.PositiveInfinity.XX)) + StartNewProperty("Size"); + if (ImGui.Float2Editor("##Size", ref size, .Zero, 0.1f, float2(0.01f, 0.01f), float.PositiveInfinity.XX)) boxCollider.Size = size; float density = boxCollider.Density; - if (ImGui.DragFloat("Density", &density, 0.0f, 0.1f, textWidth)) + StartNewProperty("Density"); + if (ImGui.DragFloat("##Density", &density, 0.0f, 0.1f)) boxCollider.Density = density; float friction = boxCollider.Friction; - if (ImGui.DragFloat("Friction", &friction, 0.0f, 0.1f, textWidth)) + StartNewProperty("Friction"); + if (ImGui.DragFloat("##Friction", &friction, 0.0f, 0.1f)) boxCollider.Friction = friction; float restitution = boxCollider.Restitution; - if (ImGui.DragFloat("Restitution", &restitution, 0.0f, 0.1f, textWidth)) + StartNewProperty("Restitution"); + if (ImGui.DragFloat("##Restitution", &restitution, 0.0f, 0.1f)) boxCollider.Restitution = restitution; float restitutionThreshold = boxCollider.RestitutionThreshold; - if (ImGui.DragFloat("RestitutionThreshold", &restitutionThreshold, 0.0f, 0.1f, textWidth)) + StartNewProperty("Restitution Threshold"); + if (ImGui.DragFloat("##RestitutionThreshold", &restitutionThreshold, 0.0f, 0.1f)) boxCollider.RestitutionThreshold = restitutionThreshold; } private static void ShowCircleCollider2DComponentEditor(Entity entity, CircleCollider2DComponent* circleCollider) { - float textWidth = ImGui.CalcTextSize("Offset".CStr()).x; - textWidth += ImGui.GetStyle().FramePadding.x * 3.0f; - float2 offset = circleCollider.Offset; - if (ImGui.EditFloat2("Offset", ref offset, .Zero, 0.1f, textWidth)) + StartNewProperty("Offset"); + if (ImGui.Float2Editor("##Offset", ref offset, .Zero, 0.1f)) circleCollider.Offset = offset; float radius = circleCollider.Radius; - if (ImGui.DragFloat("Radius", &radius, 0.0f, 0.1f, textWidth)) + StartNewProperty("Radius"); + if (ImGui.DragFloat("##Radius", &radius, 0.0f, 0.1f)) circleCollider.Radius = radius; float density = circleCollider.Density; - if (ImGui.DragFloat("Density", &density, 0.0f, 0.1f, textWidth)) + StartNewProperty("Density"); + if (ImGui.DragFloat("##Density", &density, 0.0f, 0.1f)) circleCollider.Density = density; float friction = circleCollider.Friction; - if (ImGui.DragFloat("Friction", &friction, 0.0f, 0.1f, textWidth)) + StartNewProperty("Friction"); + if (ImGui.DragFloat("##Friction", &friction, 0.0f, 0.1f)) circleCollider.Friction = friction; float restitution = circleCollider.Restitution; - if (ImGui.DragFloat("Restitution", &restitution, 0.0f, 0.1f, textWidth)) + StartNewProperty("Restitution"); + if (ImGui.DragFloat("##Restitution", &restitution, 0.0f, 0.1f)) circleCollider.Restitution = restitution; float restitutionThreshold = circleCollider.RestitutionThreshold; - if (ImGui.DragFloat("RestitutionThreshold", &restitutionThreshold, 0.0f, 0.1f, textWidth)) + StartNewProperty("Restitution Threshold"); + if (ImGui.DragFloat("##RestitutionThreshold", &restitutionThreshold, 0.0f, 0.1f)) circleCollider.RestitutionThreshold = restitutionThreshold; } /// If true handles for editing the vertices of the PolygonCollider2DComponent will be visible private static bool _editVerticesPolygonCollider2D = false; + /// Shows a collapsing header in a separate table row. + private static bool CollapsingHeader(StringView header) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + + return ImGui.CollapsingHeader("Vertices", .SpanAllColumns); + } + private static void ShowPolygonCollider2DComponentEditor(Entity entity, PolygonCollider2DComponent* polygonCollider) { - float textWidth = ImGui.CalcTextSize("Offset".CStr()).x; - textWidth += ImGui.GetStyle().FramePadding.x * 3.0f; - float2 offset = polygonCollider.Offset; - if (ImGui.EditFloat2("Offset", ref offset, .Zero, 0.1f, textWidth)) + StartNewProperty("Offset"); + if (ImGui.Float2Editor("Offset", ref offset, .Zero, 0.1f)) polygonCollider.Offset = offset; - if (ImGui.CollapsingHeader("Vertices")) + if (CollapsingHeader("Vertices")) { - ImGui.Checkbox("Show Vertex gizmos", &_editVerticesPolygonCollider2D); + StartNewProperty("Show Vertex gizmos"); + ImGui.Checkbox("##Show Vertex gizmos", &_editVerticesPolygonCollider2D); for (int i < polygonCollider.VertexCount) { - ImGui.EditFloat2(scope $"{i}", ref polygonCollider.Vertices[i]); + StartNewProperty(scope $"{i}"); + ImGui.Float2Editor(scope $"##{i}", ref polygonCollider.Vertices[i]); } + + StartNewProperty(""); + + ImGui.BeginDisabled(polygonCollider.VertexCount >= 8); + if (ImGui.Button("Add Vertex")) + polygonCollider.VertexCount++; + ImGui.EndDisabled(); + + ImGui.SameLine(); + + ImGui.BeginDisabled(polygonCollider.VertexCount <= 3); + if (ImGui.Button("Remove Vertex")) + polygonCollider.VertexCount--; + ImGui.EndDisabled(); } - ImGui.BeginDisabled(polygonCollider.VertexCount >= 8); - if (ImGui.Button("Add Vertex")) - polygonCollider.VertexCount++; - ImGui.EndDisabled(); - - ImGui.BeginDisabled(polygonCollider.VertexCount <= 3); - if (ImGui.Button("Remove Vertex")) - polygonCollider.VertexCount--; - ImGui.EndDisabled(); - float density = polygonCollider.Density; - if (ImGui.DragFloat("Density", &density, 0.0f, 0.1f, textWidth)) + StartNewProperty("Density"); + if (ImGui.DragFloat("##Density", &density, 0.0f, 0.1f)) polygonCollider.Density = density; float friction = polygonCollider.Friction; - if (ImGui.DragFloat("Friction", &friction, 0.0f, 0.1f, textWidth)) + StartNewProperty("Friction"); + if (ImGui.DragFloat("##Friction", &friction, 0.0f, 0.1f)) polygonCollider.Friction = friction; float restitution = polygonCollider.Restitution; - if (ImGui.DragFloat("Restitution", &restitution, 0.0f, 0.1f, textWidth)) + StartNewProperty("Restitution"); + if (ImGui.DragFloat("##Restitution", &restitution, 0.0f, 0.1f)) polygonCollider.Restitution = restitution; float restitutionThreshold = polygonCollider.RestitutionThreshold; - if (ImGui.DragFloat("RestitutionThreshold", &restitutionThreshold, 0.0f, 0.1f, textWidth)) + StartNewProperty("Restitution Threshold"); + if (ImGui.DragFloat("##RestitutionThreshold", &restitutionThreshold, 0.0f, 0.1f)) polygonCollider.RestitutionThreshold = restitutionThreshold; } @@ -561,7 +649,8 @@ namespace GlitchyEditor.EditWindows StringView search = StringView(); char8* scriptLabel = scriptComponent.ScriptClassName.ToScopeCStr!() ?? "Select Script..."; - + + StartNewProperty("Script"); if (ImGui.Button(scriptLabel)) ImGui.OpenPopup("SelectScript"); @@ -588,25 +677,15 @@ namespace GlitchyEditor.EditWindows ScriptEngine.ShowScriptEditor(entity, scriptComponent); } - private static void LabelColumn(StringView label) - { - ImGui.TextUnformatted(label); - ImGui.NextColumn(); - } - private static void ShowLightComponentEditor(Entity entity, LightComponent* lightComponent) { - ImGui.Columns(2); - defer ImGui.Columns(1); - const String[?] strings = String[]("Directional", "Point", "Spot"); var light = ref lightComponent.SceneLight; String typeName = strings[light.LightType.Underlying]; - LabelColumn("Type"); - + StartNewProperty("Type"); if (ImGui.BeginCombo("##Type", typeName.CStr())) { for (int i = 0; i < 3; i++) @@ -625,25 +704,20 @@ namespace GlitchyEditor.EditWindows ImGui.EndCombo(); } - ImGui.NextColumn(); - LabelColumn("Color"); - ColorRGB color = ColorRGB.LinearToSRGB(light.Color); + StartNewProperty("Color"); if (ImGui.ColorEdit3("##Color", ref color)) light.Color = ColorRGB.SRgbToLinear(color); - ImGui.NextColumn(); - LabelColumn("Illuminance"); - float illuminance = light.Illuminance; + StartNewProperty("Illuminance"); if (ImGui.DragFloat("##Illuminance", &illuminance, 0.1f, 0.0f, float.MaxValue)) light.Illuminance = illuminance; } private static void ShowMeshComponentEditor(Entity entity, MeshComponent* meshComponent) { - ImGui.TextUnformatted("Mesh:"); - ImGui.SameLine(); + StartNewProperty("Mesh"); GeometryBinding mesh = meshComponent.Mesh; @@ -697,7 +771,6 @@ namespace GlitchyEditor.EditWindows static float buttonWidth = 100; - ImGui.NewLine(); ImGui.Separator(); ImGui.NewLine(); diff --git a/GlitchyEngine/src/ImGui/ImGuiExtension.bf b/GlitchyEngine/src/ImGui/ImGuiExtension.bf index 66ef3ee..a665915 100644 --- a/GlitchyEngine/src/ImGui/ImGuiExtension.bf +++ b/GlitchyEngine/src/ImGui/ImGuiExtension.bf @@ -255,6 +255,91 @@ namespace ImGui return changed; } + + /// Control to edit a vector 2 with drag functionality and reset buttons + public static bool Float2Editor(StringView label, ref float2 value, float2 resetValues = .Zero, float dragSpeed = 0.1f, float2 minValue = .Zero, float2 maxValue = .Zero, bool2 componentEnabled = true, StringView[2] format = .()) + { + return VectorEditor<2>(label, ref *(float[2]*)&value, (float[2])resetValues, dragSpeed, (float[2])minValue, (float[2])maxValue, (bool[2])componentEnabled, format); + } + + /// Control to edit a vector 3 with drag functionality and reset buttons + public static bool Float3Editor(StringView label, ref float3 value, float3 resetValues = .Zero, float dragSpeed = 0.1f, float3 minValue = .Zero, float3 maxValue = .Zero, bool3 componentEnabled = true, StringView[3] format = .()) + { + return VectorEditor<3>(label, ref *(float[3]*)&value, (float[3])resetValues, dragSpeed, (float[3])minValue, (float[3])maxValue, (bool[3])componentEnabled, format); + } + + /// Control to edit a vector 4 with drag functionality and reset buttons + public static bool Float4Editor(StringView label, ref float4 value, float4 resetValues = .Zero, float dragSpeed = 0.1f, float4 minValue = .Zero, float4 maxValue = .Zero, bool4 componentEnabled = true, StringView[4] format = .()) + { + return VectorEditor<4>(label, ref *(float[4]*)&value, (float[4])resetValues, dragSpeed, (float[4])minValue, (float[4])maxValue, (bool[4])componentEnabled, format); + } + + public static bool VectorEditor(StringView label, ref float[NumComponents] value, float[NumComponents] resetValues = .(), float dragSpeed = 0.1f, float[NumComponents] minValue = .(), float[NumComponents] maxValue = .(), bool[NumComponents] componentEnabled = .(), StringView[NumComponents] numberFormat = .()) where NumComponents : const int32 + { + const String[?] componentNames = .("X", "Y", "Z", "W"); + const String[?] componentIds = .("##X", "##Y", "##Z", "##W"); + + bool changed = false; + + PushID(label); + defer PopID(); + + PushMultiItemsWidths(NumComponents, CalcItemWidth()); + + float lineHeight = GetFont().FontSize + GetStyle().FramePadding.y * 2.0f; + ImGui.Vec2 buttonSize = .(lineHeight + 3.0f, lineHeight); + + componentLoop: for (int i < NumComponents) + { + if (i > 0) + { + SameLine(); + } + + PushStyleColor(.Button, VectorButtonColors[i].Default.ImGuiU32); + PushStyleColor(.ButtonHovered, VectorButtonColors[i].Hovered.ImGuiU32); + PushStyleColor(.ButtonActive, VectorButtonColors[i].Active.ImGuiU32); + + ImGui.BeginDisabled(!componentEnabled[i]); + + if (Button(componentNames[i], buttonSize)) + { + value[i] = resetValues[i]; + changed = true; + } + + PushStyleVar(.ItemSpacing, Vec2.Zero); + + SameLine(); + + char8* format = "%.3f"; + + if (!numberFormat[i].IsWhiteSpace) + { + // Look if we have a format for the specific index + format = numberFormat[i].ToScopeCStr!:componentLoop(); + } + else if (!numberFormat[0].IsWhiteSpace) + { + // Try to take the first number format + format = numberFormat[0].ToScopeCStr!:componentLoop(); + } + + if (DragFloat(componentIds[i], &value[i], dragSpeed, minValue[i], maxValue[i], format)) + { + changed = true; + } + + PopStyleVar(); + + ImGui.EndDisabled(); + + PopItemWidth(); + PopStyleColor(3); + } + + return changed; + } /// Draws a rectangle with the given color. public static void DrawRect(Vec2 min, Vec2 max, Color color) @@ -364,5 +449,46 @@ namespace ImGui #unwarn return SliderScalar(label, dataType, &value, &minValue, &maxValue, format, sliderFlags); } + + public static void ListElementGrabber() + { + Window* window = GetCurrentWindow(); + if (window.SkipItems) + return; + + Context* g = GetCurrentContext(); + ref Style style = ref g.Style; + + Vec2 cursorPos = ImGui.GetCursorScreenPos(); + + float line_height = max(min(window.DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), g.FontSize); + Rect bb = .(cursorPos, Vec2(cursorPos.x + g.FontSize, cursorPos.y + line_height)); + ItemSize(bb); + if (!ItemAdd(bb, 0)) + { + SameLine(0, style.FramePadding.x * 2); + return; + } + + // Render and stay on same line + U32 text_col = GetColorU32(Col.Text); + + float bar_height = line_height / 4.0f; + + Rect topBb = .(cursorPos, (Vec2)((float2)cursorPos + float2(g.FontSize, bar_height))); + RenderFrame(topBb.Min, topBb.Max, text_col, true, 4); + + Rect middleBb = topBb; + middleBb.Min.y = cursorPos.y + line_height / 2.0f - bar_height / 2.0f; + middleBb.Max.y = middleBb.Min.y + bar_height; + RenderFrame(middleBb.Min, middleBb.Max, text_col, true, 4); + + Rect bottomBb = middleBb; + bottomBb.Max.y = cursorPos.y + line_height; + bottomBb.Min.y = bottomBb.Max.y - bar_height; + RenderFrame(bottomBb.Min, bottomBb.Max, text_col, true, 4); + + SameLine(0, style.FramePadding.x * 2.0f); + } } } \ No newline at end of file diff --git a/GlitchyEngine/src/ImGui/ImGuiLayer.bf b/GlitchyEngine/src/ImGui/ImGuiLayer.bf index cf8e879..a31d66b 100644 --- a/GlitchyEngine/src/ImGui/ImGuiLayer.bf +++ b/GlitchyEngine/src/ImGui/ImGuiLayer.bf @@ -10,6 +10,7 @@ using internal ImGui; #if GE_GRAPHICS_DX11 using GlitchyEngine.Platform.DX11; +using GlitchyEngine.Math; using internal GlitchyEngine.Platform.DX11; #endif @@ -110,11 +111,88 @@ namespace GlitchyEngine.ImGui ImGui.GetIO().Fonts.AddFontDefault(); } + ApplyDefaultStyle(); + ImGui.GetIO().Fonts.AddFontDefault(); delete fontFile; } + private void ApplyDefaultStyle(ImGui.Style* dst = null) + { + ImGui.Style* style = dst ?? ImGui.GetStyle(); + ImGui.Vec4* colors = &style.Colors; + + style.FrameRounding = 4; + style.TabRounding = 8; + style.ScrollbarSize = 20; + + style.WindowMinSize = ImGui.Vec2(100, 100); + + colors[(.)ImGui.Col.WindowBg] = ImGui.Vec4(0.118f, 0.118f, 0.118f, 1.00f); + colors[(.)ImGui.Col.FrameBg] = ImGui.Vec4(0.289f, 0.387f, 0.533f, 1.00f); + colors[(.)ImGui.Col.Header] = ImGui.Vec4(0.267f, 0.295f, 0.329f, 1.00f); + colors[(.)ImGui.Col.Tab] = ImGui.Vec4(0.473f, 0.519f, 0.580f, 1.00f); + colors[(.)ImGui.Col.TabUnfocused] = ImGui.Vec4(0.255f, 0.255f, 0.255f, 1.00f); + colors[(.)ImGui.Col.CheckMark] = ImGui.Vec4(0.851f, 0.863f, 0.900f, 1.00f); + + /*colors[(.)ImGui.Col.Text] = ImGui.Vec4(0.00f, 0.00f, 0.00f, 1.00f); + colors[(.)ImGui.Col.TextDisabled] = ImGui.Vec4(0.60f, 0.60f, 0.60f, 1.00f); + colors[(.)ImGui.Col.WindowBg] = ImGui.Vec4(0.94f, 0.94f, 0.94f, 1.00f); + colors[(.)ImGui.Col.ChildBg] = ImGui.Vec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[(.)ImGui.Col.PopupBg] = ImGui.Vec4(1.00f, 1.00f, 1.00f, 0.98f); + colors[(.)ImGui.Col.Border] = ImGui.Vec4(0.00f, 0.00f, 0.00f, 0.30f); + colors[(.)ImGui.Col.BorderShadow] = ImGui.Vec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[(.)ImGui.Col.FrameBg] = ImGui.Vec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[(.)ImGui.Col.FrameBgHovered] = ImGui.Vec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[(.)ImGui.Col.FrameBgActive] = ImGui.Vec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[(.)ImGui.Col.TitleBg] = ImGui.Vec4(0.96f, 0.96f, 0.96f, 1.00f); + colors[(.)ImGui.Col.TitleBgActive] = ImGui.Vec4(0.82f, 0.82f, 0.82f, 1.00f); + colors[(.)ImGui.Col.TitleBgCollapsed] = ImGui.Vec4(1.00f, 1.00f, 1.00f, 0.51f); + colors[(.)ImGui.Col.MenuBarBg] = ImGui.Vec4(0.86f, 0.86f, 0.86f, 1.00f); + colors[(.)ImGui.Col.ScrollbarBg] = ImGui.Vec4(0.98f, 0.98f, 0.98f, 0.53f); + colors[(.)ImGui.Col.ScrollbarGrab] = ImGui.Vec4(0.69f, 0.69f, 0.69f, 0.80f); + colors[(.)ImGui.Col.ScrollbarGrabHovered] = ImGui.Vec4(0.49f, 0.49f, 0.49f, 0.80f); + colors[(.)ImGui.Col.ScrollbarGrabActive] = ImGui.Vec4(0.49f, 0.49f, 0.49f, 1.00f); + colors[(.)ImGui.Col.CheckMark] = ImGui.Vec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[(.)ImGui.Col.SliderGrab] = ImGui.Vec4(0.26f, 0.59f, 0.98f, 0.78f); + colors[(.)ImGui.Col.SliderGrabActive] = ImGui.Vec4(0.46f, 0.54f, 0.80f, 0.60f); + colors[(.)ImGui.Col.Button] = ImGui.Vec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[(.)ImGui.Col.ButtonHovered] = ImGui.Vec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[(.)ImGui.Col.ButtonActive] = ImGui.Vec4(0.06f, 0.53f, 0.98f, 1.00f); + colors[(.)ImGui.Col.Header] = ImGui.Vec4(0.26f, 0.59f, 0.98f, 0.31f); + colors[(.)ImGui.Col.HeaderHovered] = ImGui.Vec4(0.26f, 0.59f, 0.98f, 0.80f); + colors[(.)ImGui.Col.HeaderActive] = ImGui.Vec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[(.)ImGui.Col.Separator] = ImGui.Vec4(0.39f, 0.39f, 0.39f, 0.62f); + colors[(.)ImGui.Col.SeparatorHovered] = ImGui.Vec4(0.14f, 0.44f, 0.80f, 0.78f); + colors[(.)ImGui.Col.SeparatorActive] = ImGui.Vec4(0.14f, 0.44f, 0.80f, 1.00f); + colors[(.)ImGui.Col.ResizeGrip] = ImGui.Vec4(0.35f, 0.35f, 0.35f, 0.17f); + colors[(.)ImGui.Col.ResizeGripHovered] = ImGui.Vec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[(.)ImGui.Col.ResizeGripActive] = ImGui.Vec4(0.26f, 0.59f, 0.98f, 0.95f); + colors[(.)ImGui.Col.Tab] = ImGui.Vec4(0.96f, 0.8f, 0.52f, 1.0f);//ImGui.ImLerp(colors[(.)ImGui.Col.Header], colors[(.)ImGui.Col.TitleBgActive], 0.90f); + colors[(.)ImGui.Col.TabHovered] = ImGui.Vec4(0.73f, 0.78f, 0.95f, 1.0f); + colors[(.)ImGui.Col.TabActive] = ImGui.ImLerp(colors[(.)ImGui.Col.HeaderActive], colors[(.)ImGui.Col.TitleBgActive], 0.60f); + colors[(.)ImGui.Col.TabUnfocused] = ImGui.ImLerp(colors[(.)ImGui.Col.Tab], colors[(.)ImGui.Col.TitleBg], 0.80f); + colors[(.)ImGui.Col.TabUnfocusedActive] = ImGui.ImLerp(colors[(.)ImGui.Col.TabActive], colors[(.)ImGui.Col.TitleBg], 0.40f); + colors[(.)ImGui.Col.DockingPreview] = (.)((float4)colors[(.)ImGui.Col.Header] * (float4)ImGui.Vec4(1.0f, 1.0f, 1.0f, 0.7f)); + colors[(.)ImGui.Col.DockingEmptyBg] = ImGui.Vec4(0.20f, 0.20f, 0.20f, 1.00f); + colors[(.)ImGui.Col.PlotLines] = ImGui.Vec4(0.39f, 0.39f, 0.39f, 1.00f); + colors[(.)ImGui.Col.PlotLinesHovered] = ImGui.Vec4(1.00f, 0.43f, 0.35f, 1.00f); + colors[(.)ImGui.Col.PlotHistogram] = ImGui.Vec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[(.)ImGui.Col.PlotHistogramHovered] = ImGui.Vec4(1.00f, 0.45f, 0.00f, 1.00f); + colors[(.)ImGui.Col.TableHeaderBg] = ImGui.Vec4(0.78f, 0.87f, 0.98f, 1.00f); + colors[(.)ImGui.Col.TableBorderStrong] = ImGui.Vec4(0.57f, 0.57f, 0.64f, 1.00f); // Prefer using Alpha=1.0 here + colors[(.)ImGui.Col.TableBorderLight] = ImGui.Vec4(0.68f, 0.68f, 0.74f, 1.00f); // Prefer using Alpha=1.0 here + colors[(.)ImGui.Col.TableRowBg] = ImGui.Vec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[(.)ImGui.Col.TableRowBgAlt] = ImGui.Vec4(0.30f, 0.30f, 0.30f, 0.09f); + colors[(.)ImGui.Col.TextSelectedBg] = ImGui.Vec4(0.26f, 0.59f, 0.98f, 0.35f); + colors[(.)ImGui.Col.DragDropTarget] = ImGui.Vec4(0.26f, 0.59f, 0.98f, 0.95f); + colors[(.)ImGui.Col.NavHighlight] = colors[(.)ImGui.Col.HeaderHovered]; + colors[(.)ImGui.Col.NavWindowingHighlight] = ImGui.Vec4(0.70f, 0.70f, 0.70f, 0.70f); + colors[(.)ImGui.Col.NavWindowingDimBg] = ImGui.Vec4(0.20f, 0.20f, 0.20f, 0.20f); + colors[(.)ImGui.Col.ModalWindowDimBg] = ImGui.Vec4(0.20f, 0.20f, 0.20f, 0.35f);*/ + } + public void ImGuiRender() { Debug.Profiler.ProfileFunction!(); @@ -131,6 +209,8 @@ namespace GlitchyEngine.ImGui } Begin(); + + ImGui.ShowDemoWindow(); { Debug.Profiler.ProfileScope!("ImGuiRenderEvent"); diff --git a/GlitchyEngine/src/Scripting/ScriptGlue.bf b/GlitchyEngine/src/Scripting/ScriptGlue.bf index 39ab632..9769ad8 100644 --- a/GlitchyEngine/src/Scripting/ScriptGlue.bf +++ b/GlitchyEngine/src/Scripting/ScriptGlue.bf @@ -1063,6 +1063,16 @@ static class ScriptGlue fullTypeName = Mono.mono_string_new(ScriptEngine.[Friend]s_AppDomain, context.TypeName); } +#endregion + +#region ImGui Extension + + [RegisterCall("ScriptGlue::ImGuiExtension_ListElementGrabber")] + static void ImGuiExtension_ListElementGrabber() + { + ImGui.ImGui.ListElementGrabber(); + } + #endregion private static void RegisterCall(String name, T method) where T : var diff --git a/ScriptCore/Editor/DictionaryEditor.cs b/ScriptCore/Editor/DictionaryEditor.cs index bbfbf96..460945f 100644 --- a/ScriptCore/Editor/DictionaryEditor.cs +++ b/ScriptCore/Editor/DictionaryEditor.cs @@ -4,6 +4,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Runtime.CompilerServices; namespace GlitchyEngine.Editor; @@ -17,24 +18,34 @@ public class DictionaryEditor public static object? ShowEditor(object? reference, Type fieldType, string fieldName) { - object newDictionary = EntityEditor.DidNotChange; + object? newDictionary = EntityEditor.DidNotChange; IDictionary? dictionary = reference as IDictionary; Type keyType = fieldType.GetGenericArguments()[0]; Type valueType = fieldType.GetGenericArguments()[1]; + + + ImGui.BeginDisabled(dictionary == null); + + // Open dictionary if we create a new value inside it + if (ReferenceEquals(_dictionaryForNewValue, dictionary)) + ImGui.SetNextItemOpen(true); - // 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)); - + // Close dictionary if it is null if (dictionary == null) - listOpen = false; + ImGui.SetNextItemOpen(false); + + bool listOpen = ImGui.TreeNodeEx(fieldName, ImGuiTreeNodeFlags.AllowOverlap | ImGuiTreeNodeFlags.SpanAllColumns | ImGuiTreeNodeFlags.Framed); + + ImGui.EndDisabled(); + var addButtonWidth = ImGui.CalcTextSize("+").X + 2 * ImGui.GetStyle().FramePadding.X; - var removeButtonWidth = ImGui.CalcTextSize("-").X + 2 * ImGui.GetStyle().FramePadding.X; + var removeButtonWidth = ImGui.CalcTextSize("x").X + 2 * ImGui.GetStyle().FramePadding.X; - ImGui.SameLine(ImGui.GetWindowContentRegionMax().X - addButtonWidth - ImGui.GetStyle().FramePadding.X); + ImGui.TableSetColumnIndex(1); + + ImGui.SameLine(ImGui.GetContentRegionAvail().X - addButtonWidth - removeButtonWidth - ImGui.GetStyle().FramePadding.X * 2); if (ImGui.SmallButton("+")) { @@ -53,6 +64,23 @@ public class DictionaryEditor ImGuiExtension.AttachTooltip("Add a new Entry to the dictionary."); + ImGui.SameLine(ImGui.GetContentRegionAvail().X - removeButtonWidth - ImGui.GetStyle().FramePadding.X); + + ImGui.BeginDisabled(dictionary == null); + + if (ImGui.SmallButton("x")) + { + if (dictionary != null) + { + newDictionary = null; + } + } + + ImGuiExtension.AttachTooltip("Deletes the dictionary."); + + ImGui.EndDisabled(); + + if (listOpen) { Debug.Assert(dictionary != null, "Opened tree node even though it should have been a leaf!"); @@ -67,6 +95,8 @@ public class DictionaryEditor id++; ImGui.PushID(id); + EntityEditor.BeginNewRow(); + if (entry.Key == _keyLastCreated) { // The current key is the one that was last created. Open the tree node. @@ -74,9 +104,11 @@ public class DictionaryEditor _keyLastCreated = null; } - bool isEntryOpen = ImGui.TreeNode(""); + bool isEntryOpen = ImGui.TreeNodeEx($"Entry {id}", ImGuiTreeNodeFlags.SpanAllColumns | ImGuiTreeNodeFlags.AllowOverlap); - ImGui.SameLine(ImGui.GetWindowContentRegionMax().X - removeButtonWidth); + ImGui.TableSetColumnIndex(1); + + ImGui.SameLine(ImGui.GetContentRegionAvail().X - removeButtonWidth); if (ImGui.SmallButton("-")) { @@ -87,6 +119,8 @@ public class DictionaryEditor if (isEntryOpen) { + //ImGui.SameLine(); + object? newKey = EntityEditor.ShowFieldEditor(entry.Key, entry.Key?.GetType() ?? keyType, "Key"); if (newKey != EntityEditor.DidNotChange && newKey != null) @@ -108,16 +142,20 @@ public class DictionaryEditor ImGui.PopID(); } - - + if (ReferenceEquals(_dictionaryForNewValue, dictionary)) { + EntityEditor.BeginNewRow(); + ImGui.PushID("NewEntry"); ImGui.SetNextItemOpen(true); - bool isEntryOpen = ImGui.TreeNode(""); + + bool isEntryOpen = ImGui.TreeNodeEx($"New Entry", ImGuiTreeNodeFlags.SpanAllColumns | ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.AllowOverlap); - ImGui.SameLine(ImGui.GetWindowContentRegionMax().X - removeButtonWidth); + ImGui.TableSetColumnIndex(1); + + ImGui.SameLine(ImGui.GetContentRegionAvail().X - removeButtonWidth); if (ImGui.SmallButton("-")) { diff --git a/ScriptCore/Editor/EntityEditor.cs b/ScriptCore/Editor/EntityEditor.cs index 7625f3b..21d3968 100644 --- a/ScriptCore/Editor/EntityEditor.cs +++ b/ScriptCore/Editor/EntityEditor.cs @@ -1,17 +1,20 @@ using System; using System.Collections; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text; using GlitchyEngine.Core; using GlitchyEngine.Extensions; using GlitchyEngine.Math; using ImGuiNET; +using Component = GlitchyEngine.Core.Component; namespace GlitchyEngine.Editor; @@ -51,6 +54,45 @@ internal class EntityEditor } } + public static void BeginNewRow() + { + if (!StartNewProperty_NewRow) + { + StartNewProperty_NewRow = true; + return; + } + + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + } + + public static void EndTable() + { + ImGui.EndTable(); + + ImGui.GetItemID(); + } + + public static bool StartNewProperty_NewRow = true; + + /// + /// Starts a new property by creating a new table row, writing the name in the first column and entering the second column. + /// + /// The label that will be show in the label column. + /// A string containing the propertyName as ImGui id + private static string StartNewProperty(string propertyName) + { + //BeginNewRow(); + + ImGui.TextUnformatted(propertyName); + + ImGuiExtension.AttachTooltip(propertyName); + + ImGui.TableSetColumnIndex(1); + + return $"##{propertyName}"; + } + private static bool TryShowCustomEditor(object? reference, Type fieldType, string fieldName, out object? editedValue) { editedValue = DidNotChange; @@ -124,8 +166,10 @@ internal class EntityEditor Encoding.UTF8.GetBytes(stringValue, 0, stringValue.Length, bytes, 0); ImGui.PushID("Decimal"); + + string fieldId = StartNewProperty(fieldName); - if (ImGui.InputText(fieldName, bytes, (uint)bytes.Length, ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CharsDecimal)) + if (ImGui.InputText(fieldId, bytes, (uint)bytes.Length, ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CharsDecimal)) { string text = Encoding.UTF8.GetString(bytes); @@ -142,6 +186,8 @@ internal class EntityEditor private static object? ShowPrimitiveEditor(object? reference, Type fieldType, string fieldName, IEnumerable? attributes) { + string fieldId = StartNewProperty(fieldName); + object newValue = DidNotChange; unsafe void DragScalar(ImGuiDataType dataType) where T : unmanaged @@ -200,14 +246,14 @@ internal class EntityEditor if (range?.Slider == true) { - if (ImGui.SliderScalar(fieldName, dataType, (IntPtr)(&value), (IntPtr)(&min), (IntPtr)(&max))) + if (ImGui.SliderScalar(fieldId, dataType, (IntPtr)(&value), (IntPtr)(&min), (IntPtr)(&max))) { newValue = value; } } else { - if (ImGui.DragScalar(fieldName, dataType, (IntPtr)(&value), dragSpeed, (IntPtr)(&min), (IntPtr)(&max))) + if (ImGui.DragScalar(fieldId, dataType, (IntPtr)(&value), dragSpeed, (IntPtr)(&min), (IntPtr)(&max))) { newValue = value; } @@ -219,7 +265,7 @@ internal class EntityEditor if (fieldType == typeof(bool)) { bool value = (bool)reference; - if (ImGui.Checkbox(fieldName, ref value)) + if (ImGui.Checkbox(fieldId, ref value)) newValue = value; } else if (fieldType == typeof(char)) @@ -273,7 +319,7 @@ internal class EntityEditor // Place string delimiter after encoded UTF8 sequence. buffer[encodedBytes] = (byte)'\0'; - if (ImGui.InputText(fieldName, (IntPtr)buffer, 8)) + if (ImGui.InputText(fieldId, (IntPtr)buffer, 8)) { if (buffer[0] == '\\' && buffer[1] != '\0') { @@ -334,9 +380,10 @@ internal class EntityEditor private static object ShowEnumEditor(object? reference, Type fieldType, string fieldName) { - object newValue = DidNotChange; + string fieldId = StartNewProperty(fieldName); - if (ImGui.BeginCombo(fieldName, reference?.ToString())) + object newValue = DidNotChange; + if (ImGui.BeginCombo(fieldId, reference?.ToString())) { foreach (object enumValue in Enum.GetValues(fieldType)) { @@ -358,8 +405,10 @@ internal class EntityEditor ImGui.BeginDisabled(readonlyAttribute != null); - object? newValue; + object? newValue = DidNotChange; + BeginNewRow(); + if (TryShowCustomEditor(reference, fieldType, fieldName, out newValue)) { @@ -372,7 +421,6 @@ internal class EntityEditor } else { - // TODO: Try to use a custom editor ImGui.TextColored(new Vector4(1, 0, 0, 1), $"{fieldName}: Type {fieldType} is not implemented."); } } @@ -396,12 +444,12 @@ internal class EntityEditor } else if (fieldType == typeof(bool2)) { + // TODO: The bool vector editor should not be here... + + string fieldId = StartNewProperty(fieldName); + bool2 value = (bool2)reference!; - - ImGui.TextUnformatted(fieldName); - - ImGui.SameLine(); - + if (ImGui.Checkbox($"##{fieldName}X", ref value.X)) newValue = value; @@ -412,23 +460,7 @@ internal class EntityEditor } else { - ImGui.EndDisabled(); - - // Struct - if (ImGui.TreeNode(fieldName)) - { - ImGui.BeginDisabled(readonlyAttribute != null); - - ShowEditor(fieldType, reference); - - ImGui.TreePop(); - - newValue = reference; - } - else - { - ImGui.BeginDisabled(readonlyAttribute != null); - } + newValue = ShowObjectEditor(reference, fieldType, fieldName, attributes); } } else if (fieldType.IsSubclassOf(typeof(Entity)) || fieldType == typeof(Entity)) @@ -445,50 +477,7 @@ internal class EntityEditor } else { - ImGui.EndDisabled(); - - if (ImGui.TreeNode(fieldName)) - { - ImGui.BeginDisabled(readonlyAttribute != null); - - if (reference == null) - { - ImGui.SameLine(ImGui.GetWindowContentRegionMax().X - ImGui.CalcTextSize("Create").X - 2 * ImGui.GetStyle().FramePadding.X); - - if (ImGui.BeginCombo("Create", "Create", ImGuiComboFlags.HeightSmall | ImGuiComboFlags.NoArrowButton)) - { - foreach (Type t in TypeExtension.FindDerivedTypes(fieldType)) - { - if (ImGui.Selectable(t.Name)) - { - newValue = ActivatorExtension.CreateInstanceSafe(t); - } - } - - ImGui.EndCombo(); - } - } - else - { - ImGui.SameLine(ImGui.GetWindowContentRegionMax().X - ImGui.CalcTextSize("Remove").X - 2 * ImGui.GetStyle().FramePadding.X); - - if (ImGui.SmallButton("Remove")) - { - newValue = null; - } - } - - if (reference == null) - ImGui.Text("(NULL)"); - else - ShowEditor(reference.GetType(), reference); - - ImGui.TreePop(); - } - else - { - ImGui.BeginDisabled(readonlyAttribute != null); - } + newValue = ShowObjectEditor(reference, fieldType, fieldName, attributes); } ImGui.EndDisabled(); @@ -496,12 +485,75 @@ internal class EntityEditor return newValue; } + private static object? ShowObjectEditor(object? reference, Type fieldType, string fieldName, IEnumerable attributes) + { + object? newValue = DidNotChange; + + //ReadonlyAttribute? readonlyAttribute = GetAttribute(attributes); + + // TODO: Disabling stuff + //ImGui.EndDisabled(); + + ImGui.BeginDisabled(reference == null); + + if (reference == null) + ImGui.SetNextItemOpen(false); + + bool open = ImGui.TreeNodeEx(fieldName, ImGuiTreeNodeFlags.AllowOverlap | ImGuiTreeNodeFlags.SpanAllColumns); + + ImGui.EndDisabled(); + + ImGui.TableSetColumnIndex(1); + + if (!fieldType.IsValueType) + { + if (reference == null) + { + if (ImGui.BeginCombo("##Create", "Create instance...", ImGuiComboFlags.HeightSmall | ImGuiComboFlags.NoArrowButton)) + { + foreach (Type t in TypeExtension.FindDerivedTypes(fieldType)) + { + if (ImGui.Selectable(t.Name)) + { + newValue = ActivatorExtension.CreateInstanceSafe(t); + } + } + + ImGui.EndCombo(); + } + } + else + { + if (ImGui.SmallButton("Remove")) + { + newValue = null; + } + } + } + else + { + newValue = reference; + } + + if (open) + { + //ImGui.BeginDisabled(readonlyAttribute != null); + + Debug.Assert(reference != null); + + ShowEditor(reference!.GetType(), reference); + + ImGui.TreePop(); + } + + return newValue; + } + private static object? ShowComponentDropTarget(string fieldName, Type fieldType, object? currentValue) { object? newValue = DidNotChange; - ImGui.Text($"{fieldName}: "); - ImGui.SameLine(); + string fieldId = StartNewProperty(fieldName); Component? component = currentValue as Component; @@ -573,9 +625,8 @@ internal class EntityEditor private static object? ShowEntityDropTarget(string fieldName, Type fieldType, object? currentValue) { object? newValue = DidNotChange; - - ImGui.Text($"{fieldName}: "); - ImGui.SameLine(); + + string fieldId = StartNewProperty(fieldName); string entityName = "None"; @@ -663,21 +714,22 @@ internal class EntityEditor private static object ShowStringEditor(object? reference, Type fieldType, string fieldName, IEnumerable? attributes) { + string fieldId = StartNewProperty(fieldName); + object newValue = DidNotChange; TextFieldAttribute? textField = GetAttribute(attributes); - string value = reference as string ?? $"{float.MinValue}"; + string value = reference as string ?? ""; if (textField?.Multiline == true) { - ImGui.Text(fieldName); - if (ImGui.InputTextMultiline($"##{fieldName}", ref value, 1000, new Vector2(-1.0f, ImGui.GetTextLineHeight() * textField.TextFieldLines))) + if (ImGui.InputTextMultiline(fieldId, ref value, 1000, new Vector2(-1.0f, ImGui.GetTextLineHeight() * textField.TextFieldLines))) newValue = value; } else { - if (ImGui.InputText(fieldName, ref value, 1000)) + if (ImGui.InputText(fieldId, ref value, 1000, ImGuiInputTextFlags.EnterReturnsTrue)) newValue = value; } @@ -741,6 +793,9 @@ internal class EntityEditor i++; ImGui.PushID(i); + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + if (method.GetParameters().Length > 0) { Log.Error($"Method {method.Name} cannot be executed from editor, because it expects arguments."); @@ -894,19 +949,24 @@ internal class EntityEditor object? newList = DidNotChange; IList? myList = list as IList; - // 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 | - (myList == null ? ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.NoTreePushOnOpen : ImGuiTreeNodeFlags.Framed)); - + ImGui.BeginDisabled(myList == null); + if (myList == null) - listOpen = false; + ImGui.SetNextItemOpen(false); + + bool listOpen = ImGui.TreeNodeEx(fieldName, ImGuiTreeNodeFlags.AllowOverlap | ImGuiTreeNodeFlags.SpanAllColumns | ImGuiTreeNodeFlags.Framed); + + ImGui.EndDisabled(); + + ImGui.TableSetColumnIndex(1); var addButtonWidth = ImGui.CalcTextSize("+").X + 2 * ImGui.GetStyle().FramePadding.X; var removeButtonWidth = ImGui.CalcTextSize("-").X + 2 * ImGui.GetStyle().FramePadding.X; + + // TODO: Problem: when opening the list the buttons will move - ImGui.SameLine(ImGui.GetWindowContentRegionMax().X - addButtonWidth - ImGui.GetStyle().FramePadding.X - removeButtonWidth); - + ImGui.SameLine(ImGui.GetContentRegionAvail().X - addButtonWidth - ImGui.GetStyle().FramePadding.X - removeButtonWidth); + if (ImGui.SmallButton("+")) { addElement(myList, out newList); @@ -914,7 +974,7 @@ internal class EntityEditor ImGuiExtension.AttachTooltip("Add a new Element at the end of the list."); - ImGui.SameLine(ImGui.GetWindowContentRegionMax().X - removeButtonWidth); + ImGui.SameLine(ImGui.GetContentRegionAvail().X - removeButtonWidth); ImGui.BeginDisabled(myList == null); @@ -929,8 +989,8 @@ internal class EntityEditor if (listOpen) { - Debug.Assert(myList != null, "Opened tree node even though it should have been a leaf!"); - + Debug.Assert(myList != null, "Opened tree node even though the list is null!"); + // Returns true if something was dropped into the droptarget bool DropTarget(int insertIndex, string tooltip) { @@ -973,20 +1033,23 @@ internal class EntityEditor return dropped; } - // Drop at index 0 - ImGui.Separator(); - DropTarget(0, "Drop before Element 0."); - + //// Drop at index 0 + //ImGui.Separator(); + //DropTarget(0, "Drop before Element 0."); + bool orderChanged = false; - + for (int i = 0, id = 0; i < myList!.Count; i++, id++) { ImGui.PushID(id); object element = myList[i]; + BeginNewRow(); + // A Bullet point to grab the element - ImGui.Bullet(); + ImGuiExtension.ListElementGrabber(); + if (ImGui.BeginDragDropSource(ImGuiDragDropFlags.SourceAllowNullID)) { unsafe @@ -1016,9 +1079,8 @@ internal class EntityEditor ImGui.EndPopup(); } - - ImGui.SameLine(); + StartNewProperty_NewRow = false; object? newValue = ShowFieldEditor(element, element?.GetType() ?? elementType, $"Element {i}"); // Don't apply changes, when the order changed @@ -1027,10 +1089,9 @@ internal class EntityEditor myList[i] = newValue; } - // Drop after current element - ImGui.Separator(); - - orderChanged |= DropTarget(i + 1, $"Drop after Element {i}."); + //// Drop after current element + //ImGui.Separator(); + //orderChanged |= DropTarget(i + 1, $"Drop after Element {i}."); ImGui.PopID(); } diff --git a/ScriptCore/Extensions/ImGuiExtension.cs b/ScriptCore/Extensions/ImGuiExtension.cs index 7ebfbea..b799e7b 100644 --- a/ScriptCore/Extensions/ImGuiExtension.cs +++ b/ScriptCore/Extensions/ImGuiExtension.cs @@ -66,4 +66,9 @@ public static class ImGuiExtension ImGui.EndTooltip(); } + + public static void ListElementGrabber() + { + ScriptGlue.ImGuiExtension_ListElementGrabber(); + } } diff --git a/ScriptCore/ScriptGlue.cs b/ScriptCore/ScriptGlue.cs index bbe419f..bb59e1f 100644 --- a/ScriptCore/ScriptGlue.cs +++ b/ScriptCore/ScriptGlue.cs @@ -295,4 +295,11 @@ internal static class ScriptGlue public static extern void Serialization_GetObjectTypeName(IntPtr internalContext, out string fullTypeName); #endregion + +#region ImGui + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern void ImGuiExtension_ListElementGrabber(); + +#endregion }