From 863c264001d2ae354410799f74fb3b2cb1dca5e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20L=C3=BCbe=C3=9F?= Date: Tue, 12 Apr 2022 00:48:20 +0200 Subject: [PATCH] UI Tweaks, ChildEnumerator, ComponentAdded-Handlers --- .../src/EditWindows/ComponentEditWindow.bf | 24 +- .../src/EditWindows/EntityHierarchyWindow.bf | 219 ++++++++++++------ GlitchyEditor/src/Editor.bf | 1 - GlitchyEditor/src/SettingsWindow.bf | 5 + GlitchyEngine/src/Settings.bf | 2 +- GlitchyEngine/src/World/Entity.bf | 79 ++++++- GlitchyEngine/src/World/Scene.bf | 31 ++- 7 files changed, 278 insertions(+), 83 deletions(-) diff --git a/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf b/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf index 00e84c3..2b97f63 100644 --- a/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf +++ b/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf @@ -31,19 +31,23 @@ namespace GlitchyEditor.EditWindows 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(); } @@ -73,11 +77,11 @@ namespace GlitchyEditor.EditWindows ImGui.PushID(header); - bool nodeOpen = ImGui.TreeNodeEx(header.CStr(), .DefaultOpen | .AllowItemOverlap | .Framed); + bool nodeOpen = ImGui.TreeNodeEx(header.CStr(), .DefaultOpen | .AllowItemOverlap | .Framed | .SpanFullWidth); if (showComponentContextMenu != null) { - ImGui.SameLine(ImGui.GetWindowContentRegionMax().x - ImGui.CalcTextSize("...").x); + ImGui.SameLine(ImGui.GetWindowContentRegionMax().x - ImGui.CalcTextSize("...").x - 2 * ImGui.GetStyle().FramePadding.x); if (ImGui.SmallButton("...")) { @@ -139,16 +143,22 @@ namespace GlitchyEditor.EditWindows 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)) + 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)) + 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)) + if (ImGui.EditVector3("Scale", ref scale, .One, 0.1f, textWidth)) transform.Scale = scale; } diff --git a/GlitchyEditor/src/EditWindows/EntityHierarchyWindow.bf b/GlitchyEditor/src/EditWindows/EntityHierarchyWindow.bf index c6d9c92..f160afa 100644 --- a/GlitchyEditor/src/EditWindows/EntityHierarchyWindow.bf +++ b/GlitchyEditor/src/EditWindows/EntityHierarchyWindow.bf @@ -43,7 +43,17 @@ namespace GlitchyEditor.EditWindows ShowEntityHierarchyMenuBar(); + if (ImGui.BeginPopupContextWindow(s_WindowTitle)) + { + Show_ContextMenu_Create(true, false, false); + + ImGui.EndPopup(); + } + ShowEntityHierarchy(); + + if ((ImGui.IsMouseDown(.Left) || ImGui.IsMouseDown(.Right)) && !ImGui.IsAnyItemHovered() && !ImGui.GetIO().KeyCtrl && ImGui.IsWindowHovered(.AllowWhenBlockedByPopup)) + _selectedEntities.Clear(); ImGui.End(); } @@ -88,80 +98,19 @@ namespace GlitchyEditor.EditWindows /// Deletes all selected entities and their children. internal void DeleteSelectedEntities() { - List entities = scope .(); - - for(var entity in _selectedEntities) + for (var entity in _selectedEntities) { - entities.Add(entity.Handle); - - FindChildren(entity.Handle, entities); + _scene.DestroyEntity(entity, true); } - - for(var entityId in entities) - { - Entity entity = .(entityId, _scene); - _scene.DestroyEntity(entity); - } - - _selectedEntities.Clear(); } private void ShowEntityHierarchyMenuBar() { if(ImGui.BeginMenuBar()) { - if(ImGui.BeginMenu("Create")) - { - if(ImGui.MenuItem("Empty Entity")) - { - _scene.CreateEntity(); - } + Show_ContextMenu_Create(true, true, true); - if(ImGui.IsItemHovered()) - ImGui.SetTooltip("Create a new Entity."); - - if(ImGui.MenuItem("Empty Child", null, false, !_selectedEntities.IsEmpty)) - { - var newEntity = _scene.CreateEntity(); - - var transformCmp = newEntity.GetComponent(); - // Last entity in list is the entity that has been selected last. - transformCmp.Parent = _selectedEntities.Back.Handle; - } - - if(ImGui.IsItemHovered()) - ImGui.SetTooltip("Create a new Entity that is a child of the currently selected entity."); - - if(ImGui.MenuItem("Empty Parent", null, false, !_selectedEntities.IsEmpty && AllSelectionsOnSameLevel())) - { - var commonParent = _selectedEntities.Front.GetComponent(); - - var newEntity = _scene.CreateEntity(); - - if(commonParent != null) - { - // parent of selected entities is parent of the new entity. - // (which is why this doesn't work if the entities don't have the same parent) - var newEntityTransform = newEntity.GetComponent(); - newEntityTransform.Parent = commonParent.Parent; - } - - // new entity is parent of all selected entities. - for(var selectedEntity in _selectedEntities) - { - var selectedTransform = selectedEntity.GetComponent(); - selectedTransform.Parent = newEntity.Handle; - } - } - - if(ImGui.IsItemHovered()) - ImGui.SetTooltip("Create a new Entity that is the parent of the currently selected entities."); - - ImGui.EndMenu(); - } - - if(ImGui.IsItemHovered()) - ImGui.SetTooltip("Create a new Entity."); + Show_ContextMenu_Delete(); if(ImGui.MenuItem("Delete", null, false, !_selectedEntities.IsEmpty) || (Input.IsKeyPressed(.Delete) && ImGui.IsWindowHovered())) @@ -180,6 +129,114 @@ namespace GlitchyEditor.EditWindows } } + /// Creates a new entity that is a child of the given entity. + private void CreateChild(Entity? entity) + { + var newEntity = _scene.CreateEntity(); + + var transformCmp = newEntity.GetComponent(); + // Last entity in list is the entity that has been selected last. + transformCmp.Parent = entity?.Handle ?? .InvalidEntity; + } + + /// Creates a new entity that is a parent of the selected entities. + private void CreateParent() + { + if (_selectedEntities.IsEmpty || !AllSelectionsOnSameLevel()) + { + Log.EngineLogger.Error("Cannot create parent entity."); + return; + } + + var commonParent = _selectedEntities.Front.GetComponent(); + + var newEntity = _scene.CreateEntity(); + + if(commonParent != null) + { + // parent of selected entities is parent of the new entity. + // (which is why this doesn't work if the entities don't have the same parent) + var newEntityTransform = newEntity.GetComponent(); + newEntityTransform.Parent = commonParent.Parent; + } + + // new entity is parent of all selected entities. + for(var selectedEntity in _selectedEntities) + { + var selectedTransform = selectedEntity.GetComponent(); + selectedTransform.Parent = newEntity.Handle; + } + } + + private void Show_ContextMenu_Create(bool allowEmpty = true, bool allowChild = true, bool allowParent = true) + { + if (ImGui.BeginMenu("Create")) + { + if (allowEmpty) + { + if(ImGui.MenuItem("Empty Entity")) + { + if (_selectedEntities.IsEmpty) + _scene.CreateEntity(); + else + { + Entity? parent = _selectedEntities.Back.Parent; + CreateChild(parent); + } + } + + if(ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Create a new Entity."); + } + } + + if (allowChild) + { + if(ImGui.MenuItem("Empty Child", null, false, !_selectedEntities.IsEmpty)) + { + CreateChild(_selectedEntities.Back); + } + + if(ImGui.IsItemHovered()) + ImGui.SetTooltip("Create a new Entity that is a child of the selected entity."); + } + + if (allowParent) + { + if(ImGui.MenuItem("Parent", null, false, !_selectedEntities.IsEmpty && AllSelectionsOnSameLevel())) + { + CreateParent(); + } + + if(ImGui.IsItemHovered()) + ImGui.SetTooltip("Create a new Entity that is the parent of the selected entities."); + } + + ImGui.EndMenu(); + } + + if(ImGui.IsItemHovered()) + ImGui.SetTooltip("Create a new Entity."); + } + + private bool Show_ContextMenu_Delete() + { + bool deleted = false; + + if(ImGui.MenuItem("Delete", null, false, !_selectedEntities.IsEmpty)) + { + DeleteSelectedEntities(); + + deleted = true; + } + + if(ImGui.IsItemHovered()) + ImGui.SetTooltip("Deletes the selected Entities and their children."); + + return deleted; + } + private void ImGuiPrintEntityTree(TreeNode tree) { String name = null; @@ -195,7 +252,7 @@ namespace GlitchyEditor.EditWindows name = scope:: $"Entity {(tree.Value.Handle.[Friend]Index)}"; } - ImGui.TreeNodeFlags flags = .OpenOnArrow | .DefaultOpen; + ImGui.TreeNodeFlags flags = .OpenOnArrow | .DefaultOpen | .SpanAvailWidth; if(tree.Children.Count == 0) flags |= .Leaf; @@ -207,6 +264,23 @@ namespace GlitchyEditor.EditWindows bool isOpen = ImGui.TreeNodeEx((void*)(uint)tree.Value.Handle.[Friend]Index, flags, $"{name}"); + ImGui.PushID((void*)(uint)tree.Value.Handle.[Friend]Index); + + bool deleted = false; + + if (ImGui.BeginPopupContextItem("treeNodePopup")) + { + Show_ContextMenu_Create(true, true, true); + deleted = Show_ContextMenu_Delete(); + + ImGui.EndPopup(); + } + + ImGui.PopID(); + + if (deleted) + return; + if(ImGui.BeginDragDropSource()) { ImGui.SetDragDropPayload("DND_Entity", &tree.Value, sizeof(Entity)); @@ -259,7 +333,8 @@ namespace GlitchyEditor.EditWindows ImGui.EndDragDropTarget(); } - bool clicked = ImGui.IsItemClicked(); + bool clicked = ImGui.IsItemClicked(.Left); + bool clickedRight = ImGui.IsItemClicked(.Right); if(isOpen) { @@ -271,23 +346,23 @@ namespace GlitchyEditor.EditWindows ImGui.TreePop(); } - if(clicked) + if (clicked || clickedRight) { - if(inSelectedList) + if (inSelectedList && !clickedRight) { _selectedEntities.Remove(tree.Value); + inSelectedList = false; } else { - if(!ImGui.GetIO().KeyCtrl) + if (!ImGui.GetIO().KeyCtrl && !clickedRight) { _selectedEntities.Clear(); } _selectedEntities.Add(tree.Value); + inSelectedList = true; } - - inSelectedList = !inSelectedList; } } diff --git a/GlitchyEditor/src/Editor.bf b/GlitchyEditor/src/Editor.bf index d04ffb6..27027ca 100644 --- a/GlitchyEditor/src/Editor.bf +++ b/GlitchyEditor/src/Editor.bf @@ -114,6 +114,5 @@ namespace GlitchyEditor _selectedEntities.Clear(); } - } } diff --git a/GlitchyEditor/src/SettingsWindow.bf b/GlitchyEditor/src/SettingsWindow.bf index 657be5f..a279ec5 100644 --- a/GlitchyEditor/src/SettingsWindow.bf +++ b/GlitchyEditor/src/SettingsWindow.bf @@ -51,6 +51,8 @@ namespace GlitchyEditor public this() { + _open = false; + _settings = Application.Get().Settings; Create(); @@ -117,6 +119,9 @@ namespace GlitchyEditor protected override void InternalShow() { + ImGui.Begin("Settings", &_open, .NoDocking); + defer ImGui.End(); + // Leave room for 1 line below us ImGui.BeginChild("item view", ImGui.Vec2(0, -ImGui.GetFrameHeightWithSpacing())); diff --git a/GlitchyEngine/src/Settings.bf b/GlitchyEngine/src/Settings.bf index 30825d9..639eed8 100644 --- a/GlitchyEngine/src/Settings.bf +++ b/GlitchyEngine/src/Settings.bf @@ -107,7 +107,7 @@ namespace GlitchyEngine class ImGuiSettings { [Setting("UI", "Font Size"), BonInclude] - public int32 FontSize = 16; + public int32 FontSize = 14; [Setting("UI", "Font name"), BonInclude] public readonly String FontName = new .("Fonts/CascadiaCode.ttf") ~ delete _; diff --git a/GlitchyEngine/src/World/Entity.bf b/GlitchyEngine/src/World/Entity.bf index 962838c..a29d108 100644 --- a/GlitchyEngine/src/World/Entity.bf +++ b/GlitchyEngine/src/World/Entity.bf @@ -1,4 +1,5 @@ using System; +using System.Collections; using internal GlitchyEngine.World; @@ -24,13 +25,53 @@ namespace GlitchyEngine.World _scene = scene; } + public ChildEnumerator EnumerateChildren => .(this); + public bool IsValid => _entity.IsValid; + public Entity? Parent + { + get + { + var cmp = GetComponent(); + + if (cmp.Parent == .InvalidEntity) + return null; + + return .(cmp.Parent, _scene); + } + set + { + if (value == null) + { + var cmp = GetComponent(); + cmp.Parent = .InvalidEntity; + } + else + { + Entity parent = value.Value; + + if (parent.Scene != _scene) + { + Log.EngineLogger.AssertDebug(false); + return; + } + + var cmp = GetComponent(); + cmp.Parent = parent._entity; + } + } + } + public T* AddComponent(T value = T()) where T: struct, new { Log.EngineLogger.AssertDebug(!HasComponent(), scope $"Entity already has component."); - return _scene._ecsWorld.AssignComponent(_entity, value); + T* component = _scene._ecsWorld.AssignComponent(_entity, value); + + _scene.[Friend]OnComponentAdded(this, typeof(T), component); + + return component; } public T* GetComponent() where T: struct, new @@ -51,5 +92,41 @@ namespace GlitchyEngine.World _scene._ecsWorld.RemoveComponent(_entity); } + + public struct ChildEnumerator : IEnumerator, IDisposable + { + private WorldEnumerator _transformEnum; + + private EcsEntity _entity; + private Entity _currentChild; + + public this(Entity entity) + { + _entity = entity.Handle; + _transformEnum = entity.Scene._ecsWorld.Enumerate(); + _currentChild = .(.InvalidEntity, entity.Scene); + } + + public Entity Current => _currentChild; + + public Result GetNext() mut + { + while (true) + { + (EcsEntity entity, TransformComponent* transform) = Try!(_transformEnum.GetNext()); + + if (transform.Parent == _entity) + { + _currentChild.[Friend]_entity = entity; + return .Ok(_currentChild); + } + } + } + + public void Dispose() + { + _transformEnum.Dispose(); + } + } } } diff --git a/GlitchyEngine/src/World/Scene.bf b/GlitchyEngine/src/World/Scene.bf index 07648ff..78e4152 100644 --- a/GlitchyEngine/src/World/Scene.bf +++ b/GlitchyEngine/src/World/Scene.bf @@ -1,6 +1,7 @@ using GlitchyEngine.Math; using GlitchyEngine.Renderer; using System; +using System.Collections; namespace GlitchyEngine.World { @@ -9,6 +10,8 @@ namespace GlitchyEngine.World class Scene { internal EcsWorld _ecsWorld = new .() ~ delete _; + + private Dictionary _onComponentAddedHandlers = new .() ~ delete _; public this() { @@ -20,6 +23,11 @@ namespace GlitchyEngine.World v.Sprite = new Texture2D("Textures/rocket.png"); v.Sprite.SamplerState = SamplerStateManager.PointClamp; + _onComponentAddedHandlers.Add(typeof(CameraComponent), (e, t, c) => { + CameraComponent* cameraComponent = (.)c; + + cameraComponent.Camera.SetViewportSize(e.Scene.ViewportWidth, e.Scene.ViewportHeight); + }); } public ~this() @@ -68,6 +76,7 @@ namespace GlitchyEngine.World } } + /// Creates a new Entity with the given name. public Entity CreateEntity(String name = "") { Entity entity = Entity(_ecsWorld.NewEntity(), this); @@ -79,8 +88,20 @@ namespace GlitchyEngine.World return entity; } - public void DestroyEntity(Entity entity) + /** Deletes the given entity. + * @param entity The entity to delete. + * @param destroyChildren If set to true all children of entity will be destroyed. + */ + public void DestroyEntity(Entity entity, bool destroyChildren = false) { + if (destroyChildren) + { + for (Entity child in entity.EnumerateChildren) + { + DestroyEntity(child, true); + } + } + _ecsWorld.RemoveEntity(entity.Handle); } @@ -100,5 +121,13 @@ namespace GlitchyEngine.World } } } + + private void OnComponentAdded(Entity entity, Type componentType, void* component) + { + if (_onComponentAddedHandlers.TryGetValue(componentType, let handler)) + { + handler(entity, componentType, component); + } + } } } \ No newline at end of file