Extract shared editor tree view
This commit is contained in:
@@ -162,7 +162,7 @@ inline ImVec2 ProjectBrowserPanePadding() {
|
|||||||
return ImVec2(10.0f, 8.0f);
|
return ImVec2(10.0f, 8.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline ImVec2 ProjectTreeNodeFramePadding() {
|
inline ImVec2 NavigationTreeNodeFramePadding() {
|
||||||
return ImVec2(4.0f, 3.0f);
|
return ImVec2(4.0f, 3.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,10 +226,6 @@ inline ImVec2 PopupWindowPadding() {
|
|||||||
return ImVec2(12.0f, 10.0f);
|
return ImVec2(12.0f, 10.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline ImVec2 HierarchyNodeFramePadding() {
|
|
||||||
return ImVec2(4.0f, 3.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline ImVec2 AssetTileSize() {
|
inline ImVec2 AssetTileSize() {
|
||||||
return ImVec2(104.0f, 82.0f);
|
return ImVec2(104.0f, 82.0f);
|
||||||
}
|
}
|
||||||
|
|||||||
68
editor/src/UI/TreeView.h
Normal file
68
editor/src/UI/TreeView.h
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "StyleTokens.h"
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Editor {
|
||||||
|
namespace UI {
|
||||||
|
|
||||||
|
struct TreeNodeOptions {
|
||||||
|
bool selected = false;
|
||||||
|
bool leaf = false;
|
||||||
|
bool defaultOpen = false;
|
||||||
|
bool openOnArrow = true;
|
||||||
|
bool openOnDoubleClick = true;
|
||||||
|
bool spanAvailWidth = true;
|
||||||
|
bool framePadding = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TreeNodeResult {
|
||||||
|
bool open = false;
|
||||||
|
bool clicked = false;
|
||||||
|
bool doubleClicked = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline TreeNodeResult DrawTreeNode(const void* id, const char* label, const TreeNodeOptions& options = {}) {
|
||||||
|
ImGuiTreeNodeFlags flags = 0;
|
||||||
|
if (options.openOnArrow) {
|
||||||
|
flags |= ImGuiTreeNodeFlags_OpenOnArrow;
|
||||||
|
}
|
||||||
|
if (options.openOnDoubleClick) {
|
||||||
|
flags |= ImGuiTreeNodeFlags_OpenOnDoubleClick;
|
||||||
|
}
|
||||||
|
if (options.spanAvailWidth) {
|
||||||
|
flags |= ImGuiTreeNodeFlags_SpanAvailWidth;
|
||||||
|
}
|
||||||
|
if (options.framePadding) {
|
||||||
|
flags |= ImGuiTreeNodeFlags_FramePadding;
|
||||||
|
}
|
||||||
|
if (options.leaf) {
|
||||||
|
flags |= ImGuiTreeNodeFlags_Leaf;
|
||||||
|
}
|
||||||
|
if (options.selected) {
|
||||||
|
flags |= ImGuiTreeNodeFlags_Selected;
|
||||||
|
}
|
||||||
|
if (options.defaultOpen) {
|
||||||
|
flags |= ImGuiTreeNodeFlags_DefaultOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, NavigationTreeNodeFramePadding());
|
||||||
|
const bool open = ImGui::TreeNodeEx(id, flags, "%s", label);
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
|
||||||
|
return TreeNodeResult{
|
||||||
|
open,
|
||||||
|
ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen(),
|
||||||
|
ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void EndTreeNode() {
|
||||||
|
ImGui::TreePop();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace UI
|
||||||
|
} // namespace Editor
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
#include "ScalarControls.h"
|
#include "ScalarControls.h"
|
||||||
#include "SceneStatusWidget.h"
|
#include "SceneStatusWidget.h"
|
||||||
#include "StyleTokens.h"
|
#include "StyleTokens.h"
|
||||||
|
#include "TreeView.h"
|
||||||
#include "VectorControls.h"
|
#include "VectorControls.h"
|
||||||
#include "Widgets.h"
|
#include "Widgets.h"
|
||||||
|
|
||||||
|
|||||||
@@ -14,12 +14,6 @@ struct ComponentSectionResult {
|
|||||||
bool open = false;
|
bool open = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct HierarchyNodeResult {
|
|
||||||
bool open = false;
|
|
||||||
bool clicked = false;
|
|
||||||
bool doubleClicked = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AssetTileResult {
|
struct AssetTileResult {
|
||||||
bool clicked = false;
|
bool clicked = false;
|
||||||
bool contextRequested = false;
|
bool contextRequested = false;
|
||||||
@@ -183,37 +177,6 @@ inline void DrawToolbarBreadcrumbs(
|
|||||||
ImGui::PopStyleColor(2);
|
ImGui::PopStyleColor(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline HierarchyNodeResult DrawHierarchyNode(
|
|
||||||
const void* id,
|
|
||||||
const char* label,
|
|
||||||
bool selected,
|
|
||||||
bool leaf) {
|
|
||||||
ImGuiTreeNodeFlags flags =
|
|
||||||
ImGuiTreeNodeFlags_OpenOnArrow |
|
|
||||||
ImGuiTreeNodeFlags_SpanAvailWidth |
|
|
||||||
ImGuiTreeNodeFlags_FramePadding;
|
|
||||||
if (leaf) {
|
|
||||||
flags |= ImGuiTreeNodeFlags_Leaf;
|
|
||||||
}
|
|
||||||
if (selected) {
|
|
||||||
flags |= ImGuiTreeNodeFlags_Selected;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, HierarchyNodeFramePadding());
|
|
||||||
const bool open = ImGui::TreeNodeEx(id, flags, "%s", label);
|
|
||||||
ImGui::PopStyleVar();
|
|
||||||
|
|
||||||
return HierarchyNodeResult{
|
|
||||||
open,
|
|
||||||
ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen(),
|
|
||||||
ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void EndHierarchyNode() {
|
|
||||||
ImGui::TreePop();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename DrawIconFn>
|
template <typename DrawIconFn>
|
||||||
inline AssetTileResult DrawAssetTile(
|
inline AssetTileResult DrawAssetTile(
|
||||||
const char* label,
|
const char* label,
|
||||||
|
|||||||
@@ -65,71 +65,41 @@ void HierarchyPanel::OnRenameRequested(const EntityRenameRequestedEvent& event)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HierarchyPanel::Render() {
|
void HierarchyPanel::Render() {
|
||||||
UI::PanelWindowScope panel(m_name.c_str());
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, UI::HierarchyInspectorPanelBackgroundColor());
|
||||||
if (!panel.IsOpen()) {
|
ImGui::PushStyleColor(ImGuiCol_ChildBg, UI::HierarchyInspectorPanelBackgroundColor());
|
||||||
return;
|
{
|
||||||
|
UI::PanelWindowScope panel(m_name.c_str());
|
||||||
|
if (!panel.IsOpen()) {
|
||||||
|
ImGui::PopStyleColor(2);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Actions::ObserveFocusedActionRoute(*m_context, EditorActionRoute::Hierarchy);
|
||||||
|
|
||||||
|
UI::PanelContentScope content("EntityList");
|
||||||
|
if (!content.IsOpen()) {
|
||||||
|
ImGui::PopStyleColor(2);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& sceneManager = m_context->GetSceneManager();
|
||||||
|
auto rootEntities = sceneManager.GetRootEntities();
|
||||||
|
|
||||||
|
for (auto* gameObject : rootEntities) {
|
||||||
|
RenderEntity(gameObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
Actions::HandleHierarchyBackgroundPrimaryClick(*m_context, m_renameState);
|
||||||
|
|
||||||
|
Actions::DrawHierarchyBackgroundContextPopup(*m_context);
|
||||||
|
Actions::DrawHierarchyRootDropTarget(*m_context);
|
||||||
}
|
}
|
||||||
|
ImGui::PopStyleColor(2);
|
||||||
Actions::ObserveFocusedActionRoute(*m_context, EditorActionRoute::Hierarchy);
|
|
||||||
|
|
||||||
RenderSearchBar();
|
|
||||||
std::string filter = m_searchBuffer;
|
|
||||||
|
|
||||||
UI::PanelContentScope content("EntityList");
|
|
||||||
if (!content.IsOpen()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& sceneManager = m_context->GetSceneManager();
|
|
||||||
auto rootEntities = sceneManager.GetRootEntities();
|
|
||||||
SortEntities(const_cast<std::vector<::XCEngine::Components::GameObject*>&>(rootEntities));
|
|
||||||
|
|
||||||
for (auto* gameObject : rootEntities) {
|
|
||||||
RenderEntity(gameObject, filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
Actions::HandleHierarchyBackgroundPrimaryClick(*m_context, m_renameState);
|
|
||||||
|
|
||||||
Actions::DrawHierarchyBackgroundContextPopup(*m_context);
|
|
||||||
Actions::DrawHierarchyRootDropTarget(*m_context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HierarchyPanel::RenderSearchBar() {
|
void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject) {
|
||||||
UI::PanelToolbarScope toolbar("HierarchyToolbar", UI::StandardPanelToolbarHeight());
|
|
||||||
if (!toolbar.IsOpen()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const float buttonWidth = UI::HierarchyOverflowButtonWidth();
|
|
||||||
UI::ToolbarSearchField(
|
|
||||||
"##Search",
|
|
||||||
"Search hierarchy",
|
|
||||||
m_searchBuffer,
|
|
||||||
sizeof(m_searchBuffer),
|
|
||||||
buttonWidth + UI::ToolbarSearchTrailingSpacing());
|
|
||||||
ImGui::SameLine();
|
|
||||||
if (UI::ToolbarButton("...", false, ImVec2(buttonWidth, 0.0f))) {
|
|
||||||
Actions::RequestHierarchyOptionsPopup(m_optionsPopup);
|
|
||||||
}
|
|
||||||
|
|
||||||
Actions::DrawHierarchySortOptionsPopup(
|
|
||||||
m_optionsPopup,
|
|
||||||
m_sortMode,
|
|
||||||
SortMode::Name,
|
|
||||||
SortMode::ComponentCount,
|
|
||||||
SortMode::TransformFirst,
|
|
||||||
[this](SortMode mode) {
|
|
||||||
m_sortMode = mode;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject, const std::string& filter) {
|
|
||||||
if (!gameObject) return;
|
if (!gameObject) return;
|
||||||
|
|
||||||
if (!filter.empty() && !PassesFilter(gameObject, filter)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::PushID(static_cast<int>(gameObject->GetID()));
|
ImGui::PushID(static_cast<int>(gameObject->GetID()));
|
||||||
|
|
||||||
if (m_renameState.IsEditing(gameObject->GetID())) {
|
if (m_renameState.IsEditing(gameObject->GetID())) {
|
||||||
@@ -152,11 +122,14 @@ void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject
|
|||||||
CommitRename();
|
CommitRename();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const UI::HierarchyNodeResult node = UI::DrawHierarchyNode(
|
UI::TreeNodeOptions nodeOptions;
|
||||||
|
nodeOptions.selected = m_context->GetSelectionManager().IsSelected(gameObject->GetID());
|
||||||
|
nodeOptions.leaf = gameObject->GetChildCount() == 0;
|
||||||
|
|
||||||
|
const UI::TreeNodeResult node = UI::DrawTreeNode(
|
||||||
(void*)gameObject->GetUUID(),
|
(void*)gameObject->GetUUID(),
|
||||||
gameObject->GetName().c_str(),
|
gameObject->GetName().c_str(),
|
||||||
m_context->GetSelectionManager().IsSelected(gameObject->GetID()),
|
nodeOptions);
|
||||||
gameObject->GetChildCount() == 0);
|
|
||||||
|
|
||||||
if (node.clicked) {
|
if (node.clicked) {
|
||||||
Actions::HandleHierarchySelectionClick(*m_context, gameObject->GetID(), ImGui::GetIO().KeyCtrl);
|
Actions::HandleHierarchySelectionClick(*m_context, gameObject->GetID(), ImGui::GetIO().KeyCtrl);
|
||||||
@@ -173,9 +146,9 @@ void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject
|
|||||||
|
|
||||||
if (node.open) {
|
if (node.open) {
|
||||||
for (size_t i = 0; i < gameObject->GetChildCount(); i++) {
|
for (size_t i = 0; i < gameObject->GetChildCount(); i++) {
|
||||||
RenderEntity(gameObject->GetChild(i), filter);
|
RenderEntity(gameObject->GetChild(i));
|
||||||
}
|
}
|
||||||
UI::EndHierarchyNode();
|
UI::EndTreeNode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,46 +178,5 @@ void HierarchyPanel::CancelRename() {
|
|||||||
m_renameState.Cancel();
|
m_renameState.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HierarchyPanel::PassesFilter(::XCEngine::Components::GameObject* gameObject, const std::string& filter) {
|
|
||||||
if (!gameObject) return false;
|
|
||||||
|
|
||||||
if (gameObject->GetName().find(filter) != std::string::npos) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; i < gameObject->GetChildCount(); i++) {
|
|
||||||
if (PassesFilter(gameObject->GetChild(i), filter)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HierarchyPanel::SortEntities(std::vector<::XCEngine::Components::GameObject*>& entities) {
|
|
||||||
switch (m_sortMode) {
|
|
||||||
case SortMode::Name:
|
|
||||||
std::sort(entities.begin(), entities.end(), [](::XCEngine::Components::GameObject* a, ::XCEngine::Components::GameObject* b) {
|
|
||||||
return a->GetName() < b->GetName();
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case SortMode::ComponentCount:
|
|
||||||
std::sort(entities.begin(), entities.end(), [](::XCEngine::Components::GameObject* a, ::XCEngine::Components::GameObject* b) {
|
|
||||||
return a->GetComponents<::XCEngine::Components::Component>().size() > b->GetComponents<::XCEngine::Components::Component>().size();
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case SortMode::TransformFirst:
|
|
||||||
std::sort(entities.begin(), entities.end(), [](::XCEngine::Components::GameObject* a, ::XCEngine::Components::GameObject* b) {
|
|
||||||
bool aHasTransform = a->GetComponent<::XCEngine::Components::TransformComponent>() != nullptr;
|
|
||||||
bool bHasTransform = b->GetComponent<::XCEngine::Components::TransformComponent>() != nullptr;
|
|
||||||
if (aHasTransform != bHasTransform) {
|
|
||||||
return aHasTransform;
|
|
||||||
}
|
|
||||||
return a->GetName() < b->GetName();
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,37 +118,28 @@ void ProjectPanel::RenderFolderTreeNode(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGuiTreeNodeFlags flags =
|
UI::TreeNodeOptions nodeOptions;
|
||||||
ImGuiTreeNodeFlags_OpenOnArrow |
|
nodeOptions.selected = folder->fullPath == currentFolderPath;
|
||||||
ImGuiTreeNodeFlags_OpenOnDoubleClick |
|
nodeOptions.leaf = !hasChildFolders;
|
||||||
ImGuiTreeNodeFlags_SpanAvailWidth |
|
nodeOptions.defaultOpen = IsCurrentTreeBranch(currentFolderPath, folder->fullPath);
|
||||||
ImGuiTreeNodeFlags_FramePadding;
|
|
||||||
if (!hasChildFolders) {
|
|
||||||
flags |= ImGuiTreeNodeFlags_Leaf;
|
|
||||||
}
|
|
||||||
if (folder->fullPath == currentFolderPath) {
|
|
||||||
flags |= ImGuiTreeNodeFlags_Selected;
|
|
||||||
}
|
|
||||||
if (IsCurrentTreeBranch(currentFolderPath, folder->fullPath)) {
|
|
||||||
flags |= ImGuiTreeNodeFlags_DefaultOpen;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, UI::ProjectTreeNodeFramePadding());
|
const UI::TreeNodeResult node = UI::DrawTreeNode(
|
||||||
const bool open = ImGui::TreeNodeEx((void*)folder.get(), flags, "%s", folder->name.c_str());
|
(void*)folder.get(),
|
||||||
ImGui::PopStyleVar();
|
folder->name.c_str(),
|
||||||
|
nodeOptions);
|
||||||
|
|
||||||
if (ImGui::IsItemClicked()) {
|
if (node.clicked) {
|
||||||
manager.NavigateToFolder(folder);
|
manager.NavigateToFolder(folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (open) {
|
if (node.open) {
|
||||||
for (const auto& child : folder->children) {
|
for (const auto& child : folder->children) {
|
||||||
if (!child || !child->isFolder) {
|
if (!child || !child->isFolder) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
RenderFolderTreeNode(manager, child, currentFolderPath);
|
RenderFolderTreeNode(manager, child, currentFolderPath);
|
||||||
}
|
}
|
||||||
ImGui::TreePop();
|
UI::EndTreeNode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user