using ImGui; using GlitchyEngine.World; using System; using GlitchyEngine.Math; using System.Collections; using GlitchyEngine.Renderer; using GlitchyEngine; using GlitchyEngine.Content; namespace GlitchyEditor.EditWindows { using internal GlitchyEngine.World.TransformComponent; class ComponentEditWindow : EditorWindow { public const String s_WindowTitle = "Components"; private EntityHierarchyWindow _entityHierarchyWindow; private List<(Type, ComponentAttribute attribute)> _componentTypes = new .() ~ delete _; public this(EntityHierarchyWindow entityHierarchyWindow) { _entityHierarchyWindow = entityHierarchyWindow; for (let type in Type.Types) { if (let componentAttribute = type.GetCustomAttribute()) { _componentTypes.Add((type, componentAttribute)); } } } protected override void InternalShow() { ImGui.PushStyleVar(.WindowMinSize, ImGui.Vec2(1000, 100)); if(!ImGui.Begin(s_WindowTitle, &_open, .None)) { ImGui.PopStyleVar(); ImGui.End(); return; } if(_entityHierarchyWindow.SelectedEntities.Count == 1) { Entity entity = _entityHierarchyWindow.SelectedEntities.Front; ShowComponents(entity); } ImGui.PopStyleVar(); ImGui.End(); } private void ShowComponents(Entity entity) { ShowNameComponentEditor(entity); ShowComponentEditor("Transform", entity, => ShowTransformComponentEditor); ShowComponentEditor("Camera", entity, => ShowCameraComponentEditor, => ShowComponentContextMenu); ShowComponentEditor("Sprite Renderer", entity, => ShowSpriteRendererComponentEditor, => ShowComponentContextMenu); ShowComponentEditor("Mesh Renderer", entity, => ShowMeshRendererComponentEditor, => ShowComponentContextMenu); ShowComponentEditor("Light", entity, => ShowLightComponentEditor, => ShowComponentContextMenu); ShowComponentEditor("Mesh", entity, => ShowMeshComponentEditor, => ShowComponentContextMenu); ShowComponentEditor("Rigidbody 2D", entity, => ShowRigidBody2DComponentEditor, => ShowComponentContextMenu); ShowComponentEditor("Box collider 2D", entity, => ShowBoxCollider2DComponentEditor, => ShowComponentContextMenu); ShowComponentEditor("Circle collider 2D", entity, => ShowCircleCollider2DComponentEditor, => ShowComponentContextMenu); ShowAddComponentButton(entity); } private static void ShowComponentContextMenu(Entity entity, TComponent* component) where TComponent: struct, new { if (ImGui.Selectable("Remove Component")) entity.RemoveComponent(); } private static void ShowComponentEditor(String header, Entity entity, function void(Entity, TComponent*) showComponentEditor, function void(Entity, TComponent*) showComponentContextMenu = null) where TComponent: struct, new { if (!entity.HasComponent()) return; TComponent* component = entity.GetComponent(); ImGui.PushID(header); bool nodeOpen = ImGui.TreeNodeEx(header.CStr(), .DefaultOpen | .AllowItemOverlap | .Framed | .SpanFullWidth); if (showComponentContextMenu != null) { ImGui.SameLine(ImGui.GetWindowContentRegionMax().x - ImGui.CalcTextSize("...").x - 2 * ImGui.GetStyle().FramePadding.x); if (ImGui.SmallButton("...")) { ImGui.OpenPopup("component_popup"); } if (ImGui.BeginPopup("component_popup")) { showComponentContextMenu(entity, component); ImGui.EndPopup(); } } if (nodeOpen) { showComponentEditor(entity, component); ImGui.TreePop(); } ImGui.PopID(); } private static void ShowNameComponentEditor(Entity entity) { if (!entity.HasComponent()) return; char8[256] nameBuffer = default; DebugNameComponent* component = entity.GetComponent(); String name = null; if(component != null) { name = component.DebugName; } else { name = scope:: $"Entity {entity.Handle.[Friend]Index}"; } // Copy name to buffer Internal.MemCpy(&nameBuffer, name.Ptr, Math.Min(nameBuffer.Count, name.Length)); if(ImGui.InputText("Name", &nameBuffer, nameBuffer.Count)) { if(component == null) { component = entity.AddComponent(); } component.DebugName.Clear(); component.DebugName.Append(&nameBuffer); } } private static void ShowTransformComponentEditor(Entity entity, TransformComponent* transform) { float textWidth = ImGui.CalcTextSize("Position".CStr()).x; textWidth = Math.Max(textWidth, ImGui.CalcTextSize("Rotation".CStr()).x); textWidth = Math.Max(textWidth, ImGui.CalcTextSize("Scale".CStr()).x); textWidth += ImGui.GetStyle().FramePadding.x * 3.0f; Vector3 position = transform.Position; if (ImGui.EditVector3("Position", ref position, .Zero, 0.1f, textWidth)) transform.Position = position; Vector3 rotationEuler = MathHelper.ToDegrees(transform.EditorRotationEuler); if (ImGui.EditVector3("Rotation", ref rotationEuler, .Zero, 0.1f, textWidth)) transform.EditorRotationEuler = MathHelper.ToRadians(rotationEuler); Vector3 scale = transform.Scale; if (ImGui.EditVector3("Scale", ref scale, .One, 0.1f, textWidth)) transform.Scale = scale; } static String[] strings = new String[]("Orthographic", "Perspective", "Perspective (Infinite)") ~ delete _; private static void ShowCameraComponentEditor(Entity entity, CameraComponent* cameraComponent) { ImGui.Checkbox("Primary", &cameraComponent.Primary); var camera = ref cameraComponent.Camera; String typeName = strings[camera.ProjectionType.Underlying]; if (ImGui.BeginCombo("Projection", typeName.CStr())) { for (int i = 0; i < 3; i++) { bool isSelected = (typeName == strings[i]); if (ImGui.Selectable(strings[i], isSelected)) { camera.ProjectionType = (.)i; } if (isSelected) ImGui.SetItemDefaultFocus(); } ImGui.EndCombo(); } if (camera.ProjectionType == .Perspective) { float fovY = MathHelper.ToDegrees(camera.PerspectiveFovY); if (ImGui.DragFloat("Fov Y", &fovY, 0.1f)) camera.PerspectiveFovY = MathHelper.ToRadians(fovY); float near = camera.PerspectiveNearPlane; if (ImGui.DragFloat("Near", &near, 0.1f)) camera.PerspectiveNearPlane = near; float far = camera.PerspectiveFarPlane; if (ImGui.DragFloat("Far", &far, 0.1f)) camera.PerspectiveFarPlane = far; } else if (camera.ProjectionType == .InfinitePerspective) { float fovY = MathHelper.ToDegrees(camera.PerspectiveFovY); if (ImGui.DragFloat("Vertical FOV", &fovY, 0.1f)) camera.PerspectiveFovY = MathHelper.ToRadians(fovY); float near = camera.PerspectiveNearPlane; if (ImGui.DragFloat("Near", &near, 0.1f)) camera.PerspectiveNearPlane = near; } else if (camera.ProjectionType == .Orthographic) { float size = camera.OrthographicHeight; if (ImGui.DragFloat("Size", &size, 0.1f)) camera.OrthographicHeight = size; float near = camera.OrthographicNearPlane; if (ImGui.DragFloat("Near", &near, 0.1f)) camera.OrthographicNearPlane = near; float far = camera.OrthographicFarPlane; if (ImGui.DragFloat("Far", &far, 0.1f)) camera.OrthographicFarPlane = far; } bool fixedAspectRatio = camera.FixedAspectRatio; if (ImGui.Checkbox("Fixed Aspect Ratio", &fixedAspectRatio)) camera.FixedAspectRatio = fixedAspectRatio; if (fixedAspectRatio) { float aspect = camera.AspectRatio; if (ImGui.DragFloat("Aspect Ratio", &aspect, 0.1f)) camera.AspectRatio = aspect; } } private static void ShowSpriteRendererComponentEditor(Entity entity, SpriterRendererComponent* spriteRendererComponent) { ColorRGBA spriteColor = ColorRGBA.LinearToSRGB(spriteRendererComponent.Color); if (ImGui.ColorEdit4("Color", ref spriteColor)) spriteRendererComponent.Color = ColorRGBA.SRgbToLinear(spriteColor); ImGui.Button("Texture"); if (ImGui.BeginDragDropTarget()) { ImGui.Payload* payload = ImGui.AcceptDragDropPayload("CONTENT_BROWSER_ITEM"); if (payload != null) { Log.EngineLogger.Warning(""); StringView path = .((char8*)payload.Data, (int)payload.DataSize); spriteRendererComponent.Sprite = Content.LoadAsset(path); } ImGui.EndDragDropTarget(); } ImGui.EditVector<4>("UV Transform", ref *(float[4]*)&spriteRendererComponent.UvTransform); ImGui.Checkbox("Is Circle", &spriteRendererComponent.IsCircle); } private static void ShowMeshRendererComponentEditor(Entity entity, MeshRendererComponent* meshRendererComponent) { ImGui.TextUnformatted("Material:"); ImGui.SameLine(); Material material = meshRendererComponent.Material; StringView identifier = material?.Identifier ?? "None"; ImGui.Button(identifier.ToScopeCStr!()); if (ImGui.BeginDragDropTarget()) { ImGui.Payload* payload = ImGui.AcceptDragDropPayload("CONTENT_BROWSER_ITEM"); if (payload != null) { StringView fullpath = .((char8*)payload.Data, (int)payload.DataSize); meshRendererComponent.Material = Content.LoadAsset(fullpath); } ImGui.EndDragDropTarget(); } /*Effect effect = material?.Effect; if (effect == null) return;*/ // Show a preview of the material here! } private static void ShowRigidBody2DComponentEditor(Entity entity, Rigidbody2DComponent* rigidBodyComponent) { const String[?] bodyTypeStrings = .("Static", "Dynamic", "Kinematic"); String bodyTypeName = bodyTypeStrings[rigidBodyComponent.BodyType.Underlying]; if (ImGui.BeginCombo("Type", bodyTypeName.CStr())) { for (int i = 0; i < 3; i++) { bool isSelected = (bodyTypeName == bodyTypeStrings[i]); if (ImGui.Selectable(bodyTypeStrings[i], isSelected)) { rigidBodyComponent.BodyType = (.)i; } if (isSelected) ImGui.SetItemDefaultFocus(); } ImGui.EndCombo(); } ImGui.Checkbox("Fixed Rotation", &rigidBodyComponent.FixedRotation); } private static void ShowBoxCollider2DComponentEditor(Entity entity, BoxCollider2DComponent* boxCollider) { float textWidth = ImGui.CalcTextSize("Offset".CStr()).x; textWidth += ImGui.GetStyle().FramePadding.x * 3.0f; Vector2 offset = boxCollider.Offset; if (ImGui.EditVector2("Offset", ref offset, .Zero, 0.1f, textWidth)) boxCollider.Offset = offset; Vector2 size = boxCollider.Size; if (ImGui.EditVector2("Size", ref size, .Zero, 0.1f, textWidth)) boxCollider.Size = size; float density = boxCollider.Density; if (ImGui.DragFloat("Density", &density, 0.0f, 0.1f, textWidth)) boxCollider.Density = density; float friction = boxCollider.Friction; if (ImGui.DragFloat("Friction", &friction, 0.0f, 0.1f, textWidth)) boxCollider.Friction = friction; float restitution = boxCollider.Restitution; if (ImGui.DragFloat("Restitution", &restitution, 0.0f, 0.1f, textWidth)) boxCollider.Restitution = restitution; float restitutionThreshold = boxCollider.RestitutionThreshold; if (ImGui.DragFloat("RestitutionThreshold", &restitutionThreshold, 0.0f, 0.1f, textWidth)) 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; Vector2 offset = circleCollider.Offset; if (ImGui.EditVector2("Offset", ref offset, .Zero, 0.1f, textWidth)) circleCollider.Offset = offset; float radius = circleCollider.Radius; if (ImGui.DragFloat("Radius", &radius, 0.0f, 0.1f, textWidth)) circleCollider.Radius = radius; float density = circleCollider.Density; if (ImGui.DragFloat("Density", &density, 0.0f, 0.1f, textWidth)) circleCollider.Density = density; float friction = circleCollider.Friction; if (ImGui.DragFloat("Friction", &friction, 0.0f, 0.1f, textWidth)) circleCollider.Friction = friction; float restitution = circleCollider.Restitution; if (ImGui.DragFloat("Restitution", &restitution, 0.0f, 0.1f, textWidth)) circleCollider.Restitution = restitution; float restitutionThreshold = circleCollider.RestitutionThreshold; if (ImGui.DragFloat("RestitutionThreshold", &restitutionThreshold, 0.0f, 0.1f, textWidth)) circleCollider.RestitutionThreshold = restitutionThreshold; } 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"); if (ImGui.BeginCombo("##Type", typeName.CStr())) { for (int i = 0; i < 3; i++) { bool isSelected = (typeName == strings[i]); if (ImGui.Selectable(strings[i], isSelected)) { light.LightType = (.)i; } if (isSelected) ImGui.SetItemDefaultFocus(); } ImGui.EndCombo(); } ImGui.NextColumn(); LabelColumn("Color"); ColorRGB color = ColorRGB.LinearToSRGB(light.Color); if (ImGui.ColorEdit3("##Color", ref color)) light.Color = ColorRGB.SRgbToLinear(color); ImGui.NextColumn(); LabelColumn("Illuminance"); float illuminance = light.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(); GeometryBinding mesh = meshComponent.Mesh; StringView identifier = mesh?.Identifier ?? "None"; ImGui.Button(identifier.ToScopeCStr!()); if (ImGui.BeginDragDropTarget()) { ImGui.Payload* payload = ImGui.AcceptDragDropPayload("CONTENT_BROWSER_ITEM"); if (payload != null) { StringView fullpath = .((char8*)payload.Data, (int)payload.DataSize); /*int idx = fullpath.IndexOf('#'); if (idx == -1) { // Doesn't make sense here, we NEED a sub asset Runtime.NotImplemented(); } StringView filePath = fullpath.Substring(0, idx); StringView meshName = fullpath.Substring(idx + 1);*/ meshComponent.Mesh = Content.LoadAsset(fullpath); // TODO: support multiple primitives (treat every primitive as a single mesh? or: mesh can have multiple primitives) /*using (GeometryBinding binding = ModelLoader.LoadMesh(filePath, meshName, 0)) { meshComponent.Mesh = binding; }*/ //ModelLoader.LoadModel(scope .(path), ) /*using (Texture2D newTexture = new Texture2D(path, true)) { newTexture.SamplerState = SamplerStateManager.AnisotropicWrap; material.SetTexture(texture.key, newTexture); }*/ } ImGui.EndDragDropTarget(); } } private static void ShowAddComponentButton(Entity entity) { static char8[128] searchBuffer = .(); static StringView searchFilter = .(); static float buttonWidth = 100; ImGui.NewLine(); ImGui.Separator(); ImGui.NewLine(); void ShowComponentButton(String name) where TComponent : struct, new { // If the entity already has this component, don't show the option to add it if (entity.HasComponent()) return; float textWidth = ImGui.CalcTextSize(name.CStr()).x; buttonWidth = Math.Max(buttonWidth, textWidth + ImGui.GetStyle().FramePadding.x * 2); if (name.Contains(searchFilter, true) && ImGui.Selectable(name)) { if (!entity.HasComponent()) entity.AddComponent(); } } float textWidth = ImGui.CalcTextSize("Add Component...").x; buttonWidth = Math.Max(buttonWidth, textWidth + ImGui.GetStyle().FramePadding.x * 2); ImGui.PushItemWidth(buttonWidth); ImGui.NewLine(); ImGui.SameLine(ImGui.GetContentRegionMax().x / 2 - buttonWidth / 2); if (ImGui.BeginCombo("##add_component_combo", "Add Component...", .NoArrowButton)) { if (ImGui.InputText("##search_component_name", &searchBuffer, (.)searchBuffer.Count)) { searchFilter = .(&searchBuffer); } ImGui.Separator(); ShowComponentButton("Camera"); ShowComponentButton("Sprite Renderer"); ShowComponentButton("Light"); ShowComponentButton("Rigidbody 2D"); ShowComponentButton("Box collider 2D"); ShowComponentButton("Circle collider 2D"); ShowComponentButton("Mesh"); ShowComponentButton("Mesh Renderer"); ImGui.EndCombo(); } ImGui.PopItemWidth(); } } }