Files
glitchy-engine-beef/GlitchyEditor/src/EditWindows/EntityHierarchyWindow.bf
T
Simon Lübeß 9197a209d6 Ecs changes
- BitArray has always at least sizeof(uint) bits
- Renamed Entity to EcsEntity
- Added value parameter to EcsWorld.AssignComponent
- Matched EcsWorld generic parameters
- Added Scene
- Added Entity
2022-02-28 23:34:52 +01:00

328 lines
7.5 KiB
Beef

using GlitchyEngine.Collections;
using GlitchyEngine.World;
using ImGui;
using System;
using System.Collections;
using GlitchyEngine;
namespace GlitchyEditor.EditWindows
{
using internal GlitchyEditor;
/// A window for viewing and editing the scene hierarchy
class EntityHierarchyWindow : EditorWindow
{
public const String s_WindowTitle = "Entity Hierarchy";
/// Buffer for the entity search string.
private char8[64] _entitySearchChars;
public Editor Editor => _editor;
public this(Editor editor)
{
_editor = editor;
}
protected override void InternalShow()
{
if(!ImGui.Begin(s_WindowTitle, &_open, .MenuBar))
{
ImGui.End();
return;
}
ShowEntityHierarchyMenuBar();
ShowEntityHierarchy();
ImGui.End();
}
private void ShowEntityHierarchyMenuBar()
{
if(ImGui.BeginMenuBar())
{
if(ImGui.BeginMenu("Create"))
{
if(ImGui.MenuItem("Empty Entity"))
{
_editor.CreateEntityWithTransform();
}
if(ImGui.IsItemHovered())
ImGui.SetTooltip("Create a new Entity with a transform component.");
if(ImGui.MenuItem("Empty Child", null, false, !_editor.SelectedEntities.IsEmpty))
{
var newEntity = _editor.CreateEntityWithTransform();
var parent = _editor.World.AssignComponent<ParentComponent>(newEntity);
// Last entity in list is the entity that has been selected last.
parent.Entity = _editor.SelectedEntities.Back;
}
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, !_editor.SelectedEntities.IsEmpty && _editor.AllSelectionsOnSameLevel()))
{
var commonParent = _editor.World.GetComponent<ParentComponent>(_editor.SelectedEntities.Front);
var newEntity = _editor.CreateEntityWithTransform();
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 newEntityParent = _editor.World.AssignComponent<ParentComponent>(newEntity);
newEntityParent.Entity = commonParent.Entity;
}
// new entity is parent of all selected entities.
for(var selectedEntity in _editor.SelectedEntities)
{
var selectedEntityParent = _editor.World.AssignComponent<ParentComponent>(selectedEntity);
selectedEntityParent.Entity = newEntity;
}
}
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.");
if(ImGui.MenuItem("Delete", null, false, !_editor.SelectedEntities.IsEmpty) || Input.IsKeyPressed(.Delete))
{
_editor.DeleteSelectedEntities();
}
if(ImGui.IsItemHovered())
ImGui.SetTooltip("Deletes the selected Entities and their children.");
ImGui.Text("Search:");
ImGui.InputText(String.Empty, &_entitySearchChars, (.)_entitySearchChars.Count);
ImGui.EndMenuBar();
}
}
private void ImGuiPrintEntityTree(TreeNode<EcsEntity> tree)
{
String name = null;
var nameComponent = _editor.World.GetComponent<DebugNameComponent>(tree.Value);
if(nameComponent != null)
{
name = nameComponent.DebugName;
}
else
{
name = scope:: $"Entity {(tree.Value.[Friend]Index)}";
}
ImGui.TreeNodeFlags flags = .OpenOnArrow;
if(tree.Children.Count == 0)
flags |= .Leaf;
bool inSelectedList = _editor.SelectedEntities.Contains(tree.Value);
if(inSelectedList)
flags |= .Selected;
bool isOpen = ImGui.TreeNodeEx(name, flags);
if(ImGui.BeginDragDropSource())
{
ImGui.SetDragDropPayload("DND_Entity", &tree.Value, sizeof(EcsEntity));
ImGui.Text(name);
ImGui.EndDragDropSource();
}
if(ImGui.BeginDragDropTarget())
{
ImGui.Payload* payload = ImGui.AcceptDragDropPayload("DND_Entity");
if(payload != null)
{
Log.ClientLogger.AssertDebug(payload.DataSize == sizeof(EcsEntity));
EcsEntity movedEntity = *(EcsEntity*)payload.Data;
bool dropLegal = true;
EcsEntity walker = tree.Value;
// make sure the dropped entity is not a parent of the entity we dropped it on.
while(true)
{
var walkerParent = _editor.World.GetComponent<ParentComponent>(walker);
if(walkerParent == null)
{
dropLegal = true;
break;
}
else if(walkerParent.Entity == movedEntity)
{
dropLegal = false;
break;
}
walker = walkerParent.Entity;
}
if(dropLegal)
{
var movedEntityParent = _editor.World.AssignComponent<ParentComponent>(movedEntity);
movedEntityParent.Entity = tree.Value;
}
}
ImGui.EndDragDropTarget();
}
bool clicked = ImGui.IsItemClicked();
if(isOpen)
{
for(var child in tree.Children)
{
ImGuiPrintEntityTree(child);
}
ImGui.TreePop();
}
if(clicked)
{
if(inSelectedList)
{
_editor.SelectedEntities.Remove(tree.Value);
}
else
{
if(!ImGui.GetIO().KeyCtrl)
{
_editor.SelectedEntities.Clear();
}
_editor.SelectedEntities.Add(tree.Value);
}
inSelectedList = !inSelectedList;
}
}
private void ShowEntityHierarchy()
{
StringView searchString = StringView(&_entitySearchChars);
if(searchString.Length == 0)
{
// Show entity hierarchy as tree
TreeNode<EcsEntity> root = scope .(.InvalidEntity);
TreeNode<EcsEntity> AddEntity(EcsEntity entity)
{
var parent = _editor.World.GetComponent<ParentComponent>(entity);
if(parent == null)
{
return root.AddChild(entity);
}
else
{
var parentNode = root.FindNode(parent.Entity);
if(parentNode == null)
parentNode = AddEntity(parent.Entity);
return parentNode.AddChild(entity);
}
}
for(var entity in _editor.World.Enumerate())
{
AddEntity(entity);
}
if(ImGui.TreeNodeEx("Scene", .DefaultOpen))
{
if(ImGui.BeginDragDropTarget())
{
ImGui.Payload* payload = ImGui.AcceptDragDropPayload("DND_Entity");
if(payload != null)
{
Log.ClientLogger.AssertDebug(payload.DataSize == sizeof(EcsEntity));
EcsEntity movedEntity = *(EcsEntity*)payload.Data;
_editor.World.RemoveComponent<ParentComponent>(movedEntity);
// Also mark transform as dirty
var transformComponent = _editor.World.GetComponent<TransformComponent>(movedEntity);
transformComponent?.IsDirty = true;
}
ImGui.EndDragDropTarget();
}
for(var child in root.Children)
{
ImGuiPrintEntityTree(child);
}
ImGui.TreePop();
}
}
// Otherwise
else
{
// Show search results as flat list
List<StringView> searchTokens = new:ScopedAlloc! .(searchString.Split(' ', .RemoveEmptyEntries));
worldEnumeration:
for(var entity in _editor.World.Enumerate())
{
String name = null;
var nameComponent = _editor.World.GetComponent<DebugNameComponent>(entity);
if(nameComponent != null)
{
name = nameComponent.DebugName;
}
else
{
name = scope:worldEnumeration $"Entity {entity.[Friend]Index}";
}
StringView nameView = StringView(name);
for(var token in searchTokens)
{
if(nameView.IndexOf(token, true) == -1)
{
continue worldEnumeration;
}
}
ImGuiPrintEntityTree(scope .(entity));
}
}
}
}
}