|
|
@@ -9,6 +9,8 @@
|
|
|
#include <AnKi/Resource/ImageResource.h>
|
|
|
#include <AnKi/Script/ScriptManager.h>
|
|
|
#include <AnKi/Window/Input.h>
|
|
|
+#include <AnKi/Util/Filesystem.h>
|
|
|
+#include <filesystem>
|
|
|
#include <ThirdParty/ImGui/Extra/IconsMaterialDesignIcons.h> // See all icons in https://pictogrammers.com/library/mdi/
|
|
|
|
|
|
namespace anki {
|
|
|
@@ -30,15 +32,26 @@ public:
|
|
|
}
|
|
|
};
|
|
|
|
|
|
-#define ANKI_IMGUI_PUSH_POP(func, ...) \
|
|
|
+#define ANKI_PUSH_POP(func, ...) \
|
|
|
ImGui::Push##func(__VA_ARGS__); \
|
|
|
DeferredPop ANKI_CONCATENATE(pop, __LINE__)([] { \
|
|
|
ImGui::Pop##func(); \
|
|
|
})
|
|
|
|
|
|
+/// This pushes a width for text input widgets that leaves "labelSize" chars for the label
|
|
|
+#define ANKI_PUSH_POP_TEXT_INPUT_WIDTH(labelSize) \
|
|
|
+ const F32 labelWidthBase = ImGui::GetFontSize() * labelSize; /* Some amount of width for label, based on font size. */ \
|
|
|
+ const F32 labelWidthMax = ImGui::GetContentRegionAvail().x * 0.40f; /* ...but always leave some room for framed widgets. */ \
|
|
|
+ ANKI_PUSH_POP(ItemWidth, -min(labelWidthBase, labelWidthMax))
|
|
|
+
|
|
|
EditorUi::EditorUi()
|
|
|
{
|
|
|
Logger::getSingleton().addMessageHandler(this, loggerMessageHandler);
|
|
|
+
|
|
|
+ gatherAssets(m_assetsWindow.m_assetPaths);
|
|
|
+
|
|
|
+ ANKI_CHECKF(ResourceManager::getSingleton().loadResource("EngineAssets/Editor/Material.png", m_materialIcon));
|
|
|
+ ANKI_CHECKF(ResourceManager::getSingleton().loadResource("EngineAssets/Editor/Mesh.png", m_meshIcon));
|
|
|
}
|
|
|
|
|
|
EditorUi::~EditorUi()
|
|
|
@@ -46,6 +59,73 @@ EditorUi::~EditorUi()
|
|
|
Logger::getSingleton().removeMessageHandler(this, loggerMessageHandler);
|
|
|
}
|
|
|
|
|
|
+void EditorUi::listDir(const std::filesystem::path& rootPath, const std::filesystem::path& parentPath, AssetPath& parent, U32& id)
|
|
|
+{
|
|
|
+ for(const auto& entry : std::filesystem::directory_iterator(parentPath))
|
|
|
+ {
|
|
|
+ if(entry.is_directory())
|
|
|
+ {
|
|
|
+ AssetPath& p = *parent.m_children.emplaceBack();
|
|
|
+ const std::filesystem::path rpath = std::filesystem::relative(entry, parentPath);
|
|
|
+ p.m_dirname = rpath.string().c_str();
|
|
|
+ p.m_id = id++;
|
|
|
+
|
|
|
+ listDir(rootPath, entry, p, id);
|
|
|
+ }
|
|
|
+ else if(entry.is_regular_file())
|
|
|
+ {
|
|
|
+ const String extension = entry.path().extension().string().c_str();
|
|
|
+ AssetFile file;
|
|
|
+ if(extension == ".ankitex")
|
|
|
+ {
|
|
|
+ file.m_type = AssetFileType::kTexture;
|
|
|
+ }
|
|
|
+ else if(extension == ".ankimtl")
|
|
|
+ {
|
|
|
+ file.m_type = AssetFileType::kMaterial;
|
|
|
+ }
|
|
|
+ else if(extension == ".ankimesh")
|
|
|
+ {
|
|
|
+ file.m_type = AssetFileType::kMesh;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(file.m_type != AssetFileType::kNone)
|
|
|
+ {
|
|
|
+ String rpath = std::filesystem::relative(entry.path(), rootPath).string().c_str();
|
|
|
+ if(rpath.isEmpty())
|
|
|
+ {
|
|
|
+ // Sometimes it happens with paths that have links, ignore for now
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ rpath.replaceAll("\\", "/");
|
|
|
+
|
|
|
+ const String basefname = entry.path().filename().string().c_str();
|
|
|
+
|
|
|
+ file.m_basename = basefname;
|
|
|
+ file.m_filename = rpath;
|
|
|
+
|
|
|
+ parent.m_files.emplaceBack(file);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+void EditorUi::gatherAssets(DynamicArray<AssetPath>& paths)
|
|
|
+{
|
|
|
+ U32 id = 0;
|
|
|
+ ResourceFilesystem::getSingleton().iterateAllResourceBasePaths([&](CString pathname) {
|
|
|
+ AssetPath& path = *paths.emplaceBack();
|
|
|
+ path.m_dirname = pathname;
|
|
|
+ path.m_id = id++;
|
|
|
+
|
|
|
+ std::filesystem::path stdpath(pathname.cstr());
|
|
|
+ listDir(stdpath, stdpath, path, id);
|
|
|
+
|
|
|
+ return FunctorContinue::kContinue;
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
void EditorUi::loggerMessageHandler(void* ud, const LoggerMessageInfo& info)
|
|
|
{
|
|
|
EditorUi& self = *static_cast<EditorUi*>(ud);
|
|
|
@@ -88,16 +168,16 @@ void EditorUi::draw(UiCanvas& canvas)
|
|
|
ImGui::SetWindowPos(Vec2(0.0f, 0.0f));
|
|
|
ImGui::SetWindowSize(canvas.getSizef());
|
|
|
|
|
|
- buildMainMenu();
|
|
|
+ mainMenu();
|
|
|
|
|
|
ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport(), ImGuiDockNodeFlags_PassthruCentralNode);
|
|
|
|
|
|
- buildSceneHierarchyWindow();
|
|
|
- buildSceneNodePropertiesWindow();
|
|
|
- buildConsoleWindow();
|
|
|
- buildAssetsWindow();
|
|
|
+ sceneHierarchyWindow();
|
|
|
+ sceneNodePropertiesWindow();
|
|
|
+ consoleWindow();
|
|
|
+ assetsWindow();
|
|
|
|
|
|
- buildCVarsEditorWindow();
|
|
|
+ cVarsWindow();
|
|
|
|
|
|
ImGui::End();
|
|
|
|
|
|
@@ -107,7 +187,7 @@ void EditorUi::draw(UiCanvas& canvas)
|
|
|
m_canvas = nullptr;
|
|
|
}
|
|
|
|
|
|
-void EditorUi::buildMainMenu()
|
|
|
+void EditorUi::mainMenu()
|
|
|
{
|
|
|
if(ImGui::BeginMainMenuBar())
|
|
|
{
|
|
|
@@ -120,29 +200,29 @@ void EditorUi::buildMainMenu()
|
|
|
ImGui::EndMenu();
|
|
|
}
|
|
|
|
|
|
- if(ImGui::BeginMenu(ICON_MDI_EYE " Windows"))
|
|
|
+ if(ImGui::BeginMenu(ICON_MDI_APPLICATION_OUTLINE " Windows"))
|
|
|
{
|
|
|
- if(ImGui::MenuItem(ICON_MDI_EYE " Console"))
|
|
|
+ if(ImGui::MenuItem(ICON_MDI_APPLICATION_OUTLINE " Console"))
|
|
|
{
|
|
|
m_showConsoleWindow = true;
|
|
|
}
|
|
|
|
|
|
- if(ImGui::MenuItem(ICON_MDI_EYE " SceneNode Props"))
|
|
|
+ if(ImGui::MenuItem(ICON_MDI_APPLICATION_OUTLINE " SceneNode Props"))
|
|
|
{
|
|
|
m_showSceneNodePropsWindow = true;
|
|
|
}
|
|
|
|
|
|
- if(ImGui::MenuItem(ICON_MDI_EYE " Scene Hierarchy"))
|
|
|
+ if(ImGui::MenuItem(ICON_MDI_APPLICATION_OUTLINE " Scene Hierarchy"))
|
|
|
{
|
|
|
m_showSceneHierarcyWindow = true;
|
|
|
}
|
|
|
|
|
|
- if(ImGui::MenuItem(ICON_MDI_EYE " Assets"))
|
|
|
+ if(ImGui::MenuItem(ICON_MDI_APPLICATION_OUTLINE " Assets"))
|
|
|
{
|
|
|
m_showAssetsWindow = true;
|
|
|
}
|
|
|
|
|
|
- if(ImGui::MenuItem(ICON_MDI_EYE " CVars Editor"))
|
|
|
+ if(ImGui::MenuItem(ICON_MDI_APPLICATION_OUTLINE " CVars Editor"))
|
|
|
{
|
|
|
m_showCVarEditorWindow = true;
|
|
|
}
|
|
|
@@ -150,6 +230,16 @@ void EditorUi::buildMainMenu()
|
|
|
ImGui::EndMenu();
|
|
|
}
|
|
|
|
|
|
+ // Title
|
|
|
+ {
|
|
|
+ CString text = "AnKi 3D Engine Editor";
|
|
|
+ const F32 menuBarWidth = ImGui::GetWindowWidth();
|
|
|
+ const F32 textWidth = ImGui::CalcTextSize(text.cstr()).x;
|
|
|
+ ImGui::SameLine(menuBarWidth - menuBarWidth / 2.0f - textWidth / 2.0f);
|
|
|
+
|
|
|
+ ImGui::TextUnformatted(text.cstr());
|
|
|
+ }
|
|
|
+
|
|
|
// Quit bnt
|
|
|
{
|
|
|
const Char* text = ICON_MDI_CLOSE_CIRCLE;
|
|
|
@@ -177,7 +267,7 @@ void EditorUi::buildMainMenu()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-void EditorUi::buildSceneNode(SceneNode& node)
|
|
|
+void EditorUi::sceneNode(SceneNode& node)
|
|
|
{
|
|
|
ImGui::TableNextRow();
|
|
|
ImGui::TableNextColumn();
|
|
|
@@ -210,7 +300,7 @@ void EditorUi::buildSceneNode(SceneNode& node)
|
|
|
{
|
|
|
for(SceneNode* child : node.getChildren())
|
|
|
{
|
|
|
- buildSceneNode(*child);
|
|
|
+ sceneNode(*child);
|
|
|
}
|
|
|
|
|
|
ImGui::TreePop();
|
|
|
@@ -218,7 +308,7 @@ void EditorUi::buildSceneNode(SceneNode& node)
|
|
|
ImGui::PopID();
|
|
|
}
|
|
|
|
|
|
-void EditorUi::buildSceneHierarchyWindow()
|
|
|
+void EditorUi::sceneHierarchyWindow()
|
|
|
{
|
|
|
if(!m_showSceneHierarcyWindow)
|
|
|
{
|
|
|
@@ -234,9 +324,9 @@ void EditorUi::buildSceneHierarchyWindow()
|
|
|
ImGui::SetNextWindowSize(Vec2(400.0f, viewportSize.y() - kConsoleHeight), ImGuiCond_FirstUseEver);
|
|
|
}
|
|
|
|
|
|
- if(ImGui::Begin("Scene Hierarchy", &m_showSceneHierarcyWindow, 0))
|
|
|
+ if(ImGui::Begin("Scene Hierarchy", &m_showSceneHierarcyWindow, ImGuiWindowFlags_NoCollapse))
|
|
|
{
|
|
|
- buildFilter(m_sceneHierarchyWindow.m_filter);
|
|
|
+ filter(m_sceneHierarchyWindow.m_filter);
|
|
|
|
|
|
if(ImGui::BeginChild("##tree", Vec2(0.0f), ImGuiChildFlags_Borders | ImGuiChildFlags_NavFlattened, ImGuiWindowFlags_None))
|
|
|
{
|
|
|
@@ -245,7 +335,7 @@ void EditorUi::buildSceneHierarchyWindow()
|
|
|
SceneGraph::getSingleton().visitNodes([this](SceneNode& node) {
|
|
|
if(!node.getParent() && m_sceneHierarchyWindow.m_filter.PassFilter(node.getName().cstr()))
|
|
|
{
|
|
|
- buildSceneNode(node);
|
|
|
+ sceneNode(node);
|
|
|
}
|
|
|
return true;
|
|
|
});
|
|
|
@@ -258,7 +348,7 @@ void EditorUi::buildSceneHierarchyWindow()
|
|
|
ImGui::End();
|
|
|
}
|
|
|
|
|
|
-void EditorUi::buildSceneNodePropertiesWindow()
|
|
|
+void EditorUi::sceneNodePropertiesWindow()
|
|
|
{
|
|
|
if(!m_showSceneNodePropsWindow)
|
|
|
{
|
|
|
@@ -272,15 +362,15 @@ void EditorUi::buildSceneNodePropertiesWindow()
|
|
|
// Viewport is one frame delay so do that when frame >1
|
|
|
const Vec2 viewportSize = ImGui::GetMainViewport()->WorkSize;
|
|
|
const Vec2 viewportPos = ImGui::GetMainViewport()->WorkPos;
|
|
|
- const F32 initialWidth = 400.0f;
|
|
|
+ const F32 initialWidth = 500.0f;
|
|
|
ImGui::SetNextWindowPos(Vec2(viewportSize.x() - initialWidth, viewportPos.y()), ImGuiCond_FirstUseEver);
|
|
|
ImGui::SetNextWindowSize(Vec2(initialWidth, viewportSize.y() - kConsoleHeight), ImGuiCond_FirstUseEver);
|
|
|
}
|
|
|
|
|
|
- if(ImGui::Begin("SceneNode Props", &m_showSceneNodePropsWindow, 0) && m_sceneHierarchyWindow.m_visibleNode)
|
|
|
+ if(ImGui::Begin("SceneNode Props", &m_showSceneNodePropsWindow, ImGuiWindowFlags_NoCollapse) && m_sceneHierarchyWindow.m_visibleNode)
|
|
|
{
|
|
|
- U32 id = 0;
|
|
|
SceneNode& node = *m_sceneHierarchyWindow.m_visibleNode;
|
|
|
+ I32 id = 0;
|
|
|
|
|
|
if(state.m_currentSceneNodeUuid != node.getUuid())
|
|
|
{
|
|
|
@@ -291,24 +381,13 @@ void EditorUi::buildSceneNodePropertiesWindow()
|
|
|
state.m_textEditorTxt.destroy();
|
|
|
}
|
|
|
|
|
|
- auto dummyButton = [&]() {
|
|
|
- ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.0f);
|
|
|
- ImGui::PushID(id++);
|
|
|
- ImGui::Button(ICON_MDI_LOCK);
|
|
|
- ImGui::PopID();
|
|
|
- ImGui::PopStyleVar();
|
|
|
- ImGui::SameLine();
|
|
|
- };
|
|
|
-
|
|
|
- const F32 labelWidthBase = ImGui::GetFontSize() * 12; // Some amount of width for label, based on font size.
|
|
|
- const F32 labelWidthMax = ImGui::GetContentRegionAvail().x * 0.40f; // ...but always leave some room for framed widgets.
|
|
|
- ANKI_IMGUI_PUSH_POP(ItemWidth, -min(labelWidthBase, labelWidthMax));
|
|
|
+ ANKI_PUSH_POP_TEXT_INPUT_WIDTH(12);
|
|
|
|
|
|
// Name
|
|
|
{
|
|
|
- dummyButton();
|
|
|
+ dummyButton(id++);
|
|
|
|
|
|
- Array<Char, 256> name;
|
|
|
+ Array<Char, kMaxTextInputLen> name;
|
|
|
std::strncpy(name.getBegin(), node.getName().getBegin(), name.getSize());
|
|
|
if(ImGui::InputText("Name", name.getBegin(), name.getSize(), ImGuiInputTextFlags_EnterReturnsTrue))
|
|
|
{
|
|
|
@@ -318,7 +397,7 @@ void EditorUi::buildSceneNodePropertiesWindow()
|
|
|
|
|
|
// Local transform
|
|
|
{
|
|
|
- dummyButton();
|
|
|
+ dummyButton(id++);
|
|
|
|
|
|
F32 localOrigin[3] = {node.getLocalOrigin().x(), node.getLocalOrigin().y(), node.getLocalOrigin().z()};
|
|
|
if(ImGui::DragFloat3(ICON_MDI_AXIS_ARROW " Origin", localOrigin, 0.025f, -1000000.0f, 1000000.0f))
|
|
|
@@ -364,7 +443,7 @@ void EditorUi::buildSceneNodePropertiesWindow()
|
|
|
|
|
|
// Local rotation
|
|
|
{
|
|
|
- dummyButton();
|
|
|
+ dummyButton(id++);
|
|
|
|
|
|
const Euler rot(node.getLocalRotation());
|
|
|
F32 localRotation[3] = {toDegrees(rot.x()), toDegrees(rot.y()), toDegrees(rot.z())};
|
|
|
@@ -424,16 +503,18 @@ void EditorUi::buildSceneNodePropertiesWindow()
|
|
|
|
|
|
// Components
|
|
|
{
|
|
|
- ANKI_IMGUI_PUSH_POP(StyleVar, ImGuiStyleVar_SeparatorTextAlign, Vec2(0.5f));
|
|
|
+ ANKI_PUSH_POP(StyleVar, ImGuiStyleVar_SeparatorTextAlign, Vec2(0.5f));
|
|
|
|
|
|
U32 count = 0;
|
|
|
node.iterateComponents([&](SceneComponent& comp) {
|
|
|
- ANKI_IMGUI_PUSH_POP(ID, comp.getUuid());
|
|
|
+ ANKI_PUSH_POP(ID, comp.getUuid());
|
|
|
const F32 alpha = 0.1f;
|
|
|
- ANKI_IMGUI_PUSH_POP(StyleColor, ImGuiCol_ChildBg, (count & 1) ? Vec4(0.0, 0.0f, 1.0f, alpha) : Vec4(1.0, 0.0f, 0.0f, alpha));
|
|
|
+ ANKI_PUSH_POP(StyleColor, ImGuiCol_ChildBg, (count & 1) ? Vec4(0.0, 0.0f, 1.0f, alpha) : Vec4(1.0, 0.0f, 0.0f, alpha));
|
|
|
|
|
|
if(ImGui::BeginChild("Child", Vec2(0.0f), ImGuiChildFlags_AutoResizeY))
|
|
|
{
|
|
|
+ ANKI_PUSH_POP_TEXT_INPUT_WIDTH(12);
|
|
|
+
|
|
|
// Find the icon
|
|
|
CString icon = ICON_MDI_TOY_BRICK;
|
|
|
switch(comp.getType())
|
|
|
@@ -451,7 +532,7 @@ void EditorUi::buildSceneNodePropertiesWindow()
|
|
|
label.sprintf(" %s %s (%u)", icon.cstr(), kSceneComponentTypeName[comp.getType()], comp.getUuid());
|
|
|
ImGui::SeparatorText(label.cstr());
|
|
|
|
|
|
- if(ImGui::Button(ICON_MDI_DELETE))
|
|
|
+ if(ImGui::Button(ICON_MDI_MINUS_BOX))
|
|
|
{
|
|
|
}
|
|
|
ImGui::SetItemTooltip("Delete Component");
|
|
|
@@ -470,7 +551,10 @@ void EditorUi::buildSceneNodePropertiesWindow()
|
|
|
// Nothing
|
|
|
break;
|
|
|
case SceneComponentType::kScript:
|
|
|
- buildScriptComponent(static_cast<ScriptComponent&>(comp));
|
|
|
+ scriptComponent(static_cast<ScriptComponent&>(comp));
|
|
|
+ break;
|
|
|
+ case SceneComponentType::kMaterial:
|
|
|
+ materialComponent(static_cast<MaterialComponent&>(comp));
|
|
|
break;
|
|
|
default:
|
|
|
ImGui::Text("TODO");
|
|
|
@@ -486,10 +570,20 @@ void EditorUi::buildSceneNodePropertiesWindow()
|
|
|
ImGui::End();
|
|
|
}
|
|
|
|
|
|
-void EditorUi::buildScriptComponent(ScriptComponent& comp)
|
|
|
+void EditorUi::scriptComponent(ScriptComponent& comp)
|
|
|
{
|
|
|
auto& state = m_sceneNodePropsWindow;
|
|
|
|
|
|
+ // Play button
|
|
|
+ {
|
|
|
+ ImGui::SameLine();
|
|
|
+ if(ImGui::Button(ICON_MDI_PLAY "##ScriptComponentResourceFilename"))
|
|
|
+ {
|
|
|
+ ANKI_LOGV("TODO");
|
|
|
+ }
|
|
|
+ ImGui::SetItemTooltip("Play script");
|
|
|
+ }
|
|
|
+
|
|
|
// Clear button
|
|
|
{
|
|
|
if(ImGui::Button(ICON_MDI_DELETE "##ScriptComponentResourceFilename"))
|
|
|
@@ -538,7 +632,7 @@ void EditorUi::buildScriptComponent(ScriptComponent& comp)
|
|
|
|
|
|
if(state.m_textEditorOpen && state.m_scriptComponentThatHasTheTextEditorOpen == comp.getUuid())
|
|
|
{
|
|
|
- if(buildTextEditorWindow(String().sprintf("ScriptComponent %u", comp.getUuid()), &state.m_textEditorOpen, state.m_textEditorTxt))
|
|
|
+ if(textEditorWindow(String().sprintf("ScriptComponent %u", comp.getUuid()), &state.m_textEditorOpen, state.m_textEditorTxt))
|
|
|
{
|
|
|
ANKI_LOGV("Updating ScriptComponent");
|
|
|
comp.setScriptText(state.m_textEditorTxt);
|
|
|
@@ -552,36 +646,93 @@ void EditorUi::buildScriptComponent(ScriptComponent& comp)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-void EditorUi::buildCVarsEditorWindow()
|
|
|
+void EditorUi::materialComponent(MaterialComponent& comp)
|
|
|
+{
|
|
|
+ // Locate button
|
|
|
+ {
|
|
|
+ ImGui::BeginDisabled(!comp.hasMaterialResource());
|
|
|
+ if(ImGui::Button(comp.hasMaterialResource() ? ICON_MDI_MAP_MARKER : ICON_MDI_ALERT_CIRCLE "##MaterialCompBtn"))
|
|
|
+ {
|
|
|
+ ANKI_LOGW("TODO");
|
|
|
+ }
|
|
|
+ ImGui::SetItemTooltip("Locate");
|
|
|
+ ImGui::EndDisabled();
|
|
|
+ ImGui::SameLine();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Filename
|
|
|
+ {
|
|
|
+ ImGui::SetNextItemWidth(-1.0f);
|
|
|
+
|
|
|
+ Char buff[kMaxTextInputLen] = "";
|
|
|
+ if(comp.hasMaterialResource())
|
|
|
+ {
|
|
|
+ std::strncpy(buff, comp.getMaterialFilename().cstr(), sizeof(buff));
|
|
|
+ }
|
|
|
+
|
|
|
+ if(ImGui::InputTextWithHint("##MaterialCompFname", ".ankimtl Filename", buff, sizeof(buff)))
|
|
|
+ {
|
|
|
+ comp.setMaterialFilename(buff);
|
|
|
+ }
|
|
|
+
|
|
|
+ if(comp.hasMaterialResource())
|
|
|
+ {
|
|
|
+ ImGui::SetItemTooltip("%s", comp.getMaterialFilename().cstr());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Submesh ID
|
|
|
+ {
|
|
|
+ dummyButton(0);
|
|
|
+
|
|
|
+ I32 value = comp.getSubmeshIndex();
|
|
|
+ Char txt[100] = "lala";
|
|
|
+ if(ImGui::InputInt(ICON_MDI_VECTOR_POLYGON " Submesh ID", &value, 1, 1, 0))
|
|
|
+ {
|
|
|
+ comp.setSubmeshIndex(value);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void EditorUi::cVarsWindow()
|
|
|
{
|
|
|
if(!m_showCVarEditorWindow)
|
|
|
{
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
+ F32 maxCvarTextSize = 0.0f;
|
|
|
+ CVarSet::getSingleton().iterateCVars([&](CVar& cvar) {
|
|
|
+ maxCvarTextSize = max(maxCvarTextSize, ImGui::CalcTextSize(cvar.getName().cstr()).x);
|
|
|
+ return FunctorContinue::kContinue;
|
|
|
+ });
|
|
|
+
|
|
|
if(ImGui::GetFrameCount() > 1)
|
|
|
{
|
|
|
// Viewport is one frame delay so do that when frame >1
|
|
|
const Vec2 viewportSize = ImGui::GetMainViewport()->WorkSize;
|
|
|
const Vec2 viewportPos = ImGui::GetMainViewport()->WorkPos;
|
|
|
- const Vec2 initialSize = Vec2(800.0f, m_canvas->getSizef().y() * 0.8f);
|
|
|
+ const Vec2 initialSize = Vec2(900.0f, m_canvas->getSizef().y() * 0.8f);
|
|
|
ImGui::SetNextWindowSize(initialSize, ImGuiCond_FirstUseEver);
|
|
|
ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Once, Vec2(0.5f));
|
|
|
}
|
|
|
|
|
|
if(ImGui::Begin("CVars Editor", &m_showCVarEditorWindow, 0))
|
|
|
{
|
|
|
- buildFilter(m_cvarsEditorWindow.m_filter);
|
|
|
+ filter(m_cvarsEditorWindow.m_filter);
|
|
|
|
|
|
- if(ImGui::BeginChild("TableInternals", Vec2(0.0f), ImGuiChildFlags_Borders | ImGuiChildFlags_NavFlattened))
|
|
|
+ if(ImGui::BeginChild("##Child", Vec2(0.0f), ImGuiChildFlags_Borders | ImGuiChildFlags_NavFlattened))
|
|
|
{
|
|
|
if(ImGui::BeginTable("Table", 2, 0))
|
|
|
{
|
|
|
+ ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, maxCvarTextSize + 20.0f);
|
|
|
+ ImGui::TableSetupColumn("Value");
|
|
|
+
|
|
|
I32 id = 0;
|
|
|
CVarSet::getSingleton().iterateCVars([&](CVar& cvar) {
|
|
|
if(!m_cvarsEditorWindow.m_filter.PassFilter(cvar.getName().cstr()))
|
|
|
{
|
|
|
- return false;
|
|
|
+ return FunctorContinue::kContinue;
|
|
|
}
|
|
|
|
|
|
ImGui::TableNextRow();
|
|
|
@@ -593,6 +744,8 @@ void EditorUi::buildCVarsEditorWindow()
|
|
|
|
|
|
ImGui::PushID(id++);
|
|
|
|
|
|
+ ImGui::SetNextItemWidth(-1.0f);
|
|
|
+
|
|
|
if(cvar.getValueType() == CVarValueType::kBool)
|
|
|
{
|
|
|
BoolCVar& bcvar = static_cast<BoolCVar&>(cvar);
|
|
|
@@ -658,7 +811,7 @@ void EditorUi::buildCVarsEditorWindow()
|
|
|
|
|
|
ImGui::PopID();
|
|
|
|
|
|
- return false;
|
|
|
+ return FunctorContinue::kContinue;
|
|
|
});
|
|
|
|
|
|
ImGui::EndTable();
|
|
|
@@ -670,7 +823,7 @@ void EditorUi::buildCVarsEditorWindow()
|
|
|
ImGui::End();
|
|
|
}
|
|
|
|
|
|
-void EditorUi::buildConsoleWindow()
|
|
|
+void EditorUi::consoleWindow()
|
|
|
{
|
|
|
if(!m_showConsoleWindow)
|
|
|
{
|
|
|
@@ -690,7 +843,7 @@ void EditorUi::buildConsoleWindow()
|
|
|
ImGui::SetNextWindowPos(initialPos, ImGuiCond_FirstUseEver);
|
|
|
}
|
|
|
|
|
|
- if(ImGui::Begin("Console", &m_showConsoleWindow, 0))
|
|
|
+ if(ImGui::Begin("Console", &m_showConsoleWindow, ImGuiWindowFlags_NoCollapse))
|
|
|
{
|
|
|
// Log controls
|
|
|
{
|
|
|
@@ -754,13 +907,61 @@ void EditorUi::buildConsoleWindow()
|
|
|
ImGui::End();
|
|
|
}
|
|
|
|
|
|
-void EditorUi::buildAssetsWindow()
|
|
|
+void EditorUi::dirTree(const AssetPath& path)
|
|
|
+{
|
|
|
+ auto& state = m_assetsWindow;
|
|
|
+
|
|
|
+ ImGui::TableNextRow();
|
|
|
+ ImGui::TableNextColumn();
|
|
|
+
|
|
|
+ ImGuiTreeNodeFlags treeFlags = ImGuiTreeNodeFlags_None;
|
|
|
+ treeFlags |= ImGuiTreeNodeFlags_OpenOnArrow
|
|
|
+ | ImGuiTreeNodeFlags_OpenOnDoubleClick; // Standard opening mode as we are likely to want to add selection afterwards
|
|
|
+ treeFlags |= ImGuiTreeNodeFlags_NavLeftJumpsToParent; // Left arrow support
|
|
|
+ treeFlags |= ImGuiTreeNodeFlags_SpanFullWidth; // Span full width for easier mouse reach
|
|
|
+ treeFlags |= ImGuiTreeNodeFlags_DrawLinesToNodes; // Always draw hierarchy outlines
|
|
|
+
|
|
|
+ if(state.m_pathSelected == &path)
|
|
|
+ {
|
|
|
+ treeFlags |= ImGuiTreeNodeFlags_Selected;
|
|
|
+ }
|
|
|
+
|
|
|
+ const Bool hasChildren = path.m_children.getSize();
|
|
|
+ if(!hasChildren)
|
|
|
+ {
|
|
|
+ treeFlags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_Bullet;
|
|
|
+ }
|
|
|
+
|
|
|
+ ImGui::PushID(path.m_id);
|
|
|
+ const Bool nodeOpen = ImGui::TreeNodeEx("", treeFlags, "%s", path.m_dirname.cstr());
|
|
|
+ ImGui::PopID();
|
|
|
+ ImGui::SetItemTooltip("%s", path.m_dirname.cstr());
|
|
|
+
|
|
|
+ if(ImGui::IsItemFocused())
|
|
|
+ {
|
|
|
+ state.m_pathSelected = &path;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(nodeOpen)
|
|
|
+ {
|
|
|
+ for(const AssetPath& p : path.m_children)
|
|
|
+ {
|
|
|
+ dirTree(p);
|
|
|
+ }
|
|
|
+
|
|
|
+ ImGui::TreePop();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void EditorUi::assetsWindow()
|
|
|
{
|
|
|
if(!m_showAssetsWindow)
|
|
|
{
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
+ auto& state = m_assetsWindow;
|
|
|
+
|
|
|
if(ImGui::GetFrameCount() > 1)
|
|
|
{
|
|
|
// Viewport is one frame delay so do that when frame >1
|
|
|
@@ -772,14 +973,122 @@ void EditorUi::buildAssetsWindow()
|
|
|
ImGui::SetNextWindowPos(initialPos, ImGuiCond_FirstUseEver);
|
|
|
}
|
|
|
|
|
|
- if(ImGui::Begin("Assets", &m_showAssetsWindow, 0))
|
|
|
+ if(ImGui::Begin("Assets", &m_showAssetsWindow, ImGuiWindowFlags_NoCollapse))
|
|
|
{
|
|
|
- }
|
|
|
+ // Left side
|
|
|
+ {
|
|
|
+ if(ImGui::BeginChild("Left", Vec2(300.0f, -1.0f), ImGuiChildFlags_ResizeX | ImGuiChildFlags_Borders | ImGuiChildFlags_NavFlattened))
|
|
|
+ {
|
|
|
+ if(ImGui::BeginTable("##bg", 1, ImGuiTableFlags_RowBg))
|
|
|
+ {
|
|
|
+ for(const AssetPath& p : state.m_assetPaths)
|
|
|
+ {
|
|
|
+ dirTree(p);
|
|
|
+ }
|
|
|
+
|
|
|
+ ImGui::EndTable();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ImGui::EndChild();
|
|
|
+ } // Left side
|
|
|
+
|
|
|
+ ImGui::SameLine();
|
|
|
+
|
|
|
+ // Right side
|
|
|
+ {
|
|
|
+ // Use the filter to gather the files
|
|
|
+ DynamicArray<const AssetFile*> filteredFiles;
|
|
|
+ if(state.m_pathSelected)
|
|
|
+ {
|
|
|
+ for(const AssetFile& f : state.m_pathSelected->m_files)
|
|
|
+ {
|
|
|
+ if(state.m_fileFilter.PassFilter(f.m_basename.cstr()))
|
|
|
+ {
|
|
|
+ filteredFiles.emplaceBack(&f);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if(ImGui::BeginChild("Right", Vec2(-1.0f, -1.0f), 0))
|
|
|
+ {
|
|
|
+ // Increase/decrease icon size
|
|
|
+ {
|
|
|
+ ImGui::TextUnformatted("Icon Size");
|
|
|
+ ImGui::SameLine();
|
|
|
+ ImGui::SetNextItemWidth(64.0f);
|
|
|
+ ImGui::SliderInt("##Icon Size", &state.m_cellSize, 5, 11, "%d", ImGuiSliderFlags_AlwaysClamp);
|
|
|
+ ImGui::SameLine();
|
|
|
+ }
|
|
|
+
|
|
|
+ filter(state.m_fileFilter);
|
|
|
+
|
|
|
+ if(ImGui::BeginChild("RightBottom", Vec2(-1.0f, -1.0f), 0))
|
|
|
+ {
|
|
|
+ const F32 cellWidth = F32(state.m_cellSize) * 16;
|
|
|
+ const U32 columnCount = U32(ImGui::GetContentRegionAvail().x / cellWidth);
|
|
|
+ ImGui::SetNextItemWidth(-1.0f);
|
|
|
+ const ImGuiTableFlags flags = ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter
|
|
|
+ | ImGuiTableFlags_BordersV | ImGuiTableFlags_ContextMenuInBody;
|
|
|
+ if(filteredFiles.getSize() && ImGui::BeginTable("Grid", columnCount, flags))
|
|
|
+ {
|
|
|
+ const U32 rowCount = (filteredFiles.getSize() + columnCount - 1) / columnCount;
|
|
|
+
|
|
|
+ for(U32 row = 0; row < rowCount; ++row)
|
|
|
+ {
|
|
|
+ ImGui::TableNextRow();
|
|
|
+ for(U32 column = 0; column < columnCount; ++column)
|
|
|
+ {
|
|
|
+ ImGui::TableNextColumn();
|
|
|
+
|
|
|
+ const U32 idx = row * columnCount + column;
|
|
|
+ if(idx < filteredFiles.getSize())
|
|
|
+ {
|
|
|
+ const AssetFile& file = *filteredFiles[idx];
|
|
|
+
|
|
|
+ if(file.m_type == AssetFileType::kMaterial)
|
|
|
+ {
|
|
|
+ ImTextureID id;
|
|
|
+ id.m_texture = &m_materialIcon->getTexture();
|
|
|
+ ImGui::Image(id, Vec2(cellWidth));
|
|
|
+ }
|
|
|
+ else if(file.m_type == AssetFileType::kMesh)
|
|
|
+ {
|
|
|
+ ImTextureID id;
|
|
|
+ id.m_texture = &m_meshIcon->getTexture();
|
|
|
+ ImGui::Image(id, Vec2(cellWidth));
|
|
|
+ }
|
|
|
+ else if(file.m_type == AssetFileType::kTexture)
|
|
|
+ {
|
|
|
+ ImageResourcePtr img;
|
|
|
+ loadImageToCache(file.m_filename, img);
|
|
|
+ ImTextureID id;
|
|
|
+ id.m_texture = &img->getTexture();
|
|
|
+ id.m_textureSubresource = TextureSubresourceDesc::all();
|
|
|
+ ImGui::Image(id, Vec2(cellWidth));
|
|
|
+ }
|
|
|
+
|
|
|
+ ImGui::TextWrapped("%s", file.m_basename.cstr());
|
|
|
+ ImGui::SetItemTooltip("%s", file.m_filename.cstr());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
+ ImGui::EndTable();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ ImGui::TextUnformatted("Empty");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ImGui::EndChild();
|
|
|
+ }
|
|
|
+ ImGui::EndChild();
|
|
|
+ } // Right side
|
|
|
+ }
|
|
|
ImGui::End();
|
|
|
}
|
|
|
|
|
|
-void EditorUi::buildFilter(ImGuiTextFilter& filter)
|
|
|
+void EditorUi::filter(ImGuiTextFilter& filter)
|
|
|
{
|
|
|
ImGui::SetNextItemWidth(-FLT_MIN);
|
|
|
ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F, ImGuiInputFlags_Tooltip);
|
|
|
@@ -792,7 +1101,17 @@ void EditorUi::buildFilter(ImGuiTextFilter& filter)
|
|
|
ImGui::PopItemFlag();
|
|
|
}
|
|
|
|
|
|
-Bool EditorUi::buildTextEditorWindow(CString extraWindowTitle, Bool* pOpen, String& inout)
|
|
|
+void EditorUi::dummyButton(I32 id)
|
|
|
+{
|
|
|
+ ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.0f);
|
|
|
+ ImGui::PushID(id);
|
|
|
+ ImGui::Button(ICON_MDI_LOCK);
|
|
|
+ ImGui::PopID();
|
|
|
+ ImGui::PopStyleVar();
|
|
|
+ ImGui::SameLine();
|
|
|
+};
|
|
|
+
|
|
|
+Bool EditorUi::textEditorWindow(CString extraWindowTitle, Bool* pOpen, String& inout) const
|
|
|
{
|
|
|
Bool save = false;
|
|
|
|
|
|
@@ -849,4 +1168,51 @@ Bool EditorUi::buildTextEditorWindow(CString extraWindowTitle, Bool* pOpen, Stri
|
|
|
return save;
|
|
|
}
|
|
|
|
|
|
+void EditorUi::loadImageToCache(CString fname, ImageResourcePtr& img)
|
|
|
+{
|
|
|
+ // Try to load first
|
|
|
+ ANKI_CHECKF(ResourceManager::getSingleton().loadResource(fname, img));
|
|
|
+
|
|
|
+ // Update the cache
|
|
|
+ const U32 crntFrame = ImGui::GetFrameCount();
|
|
|
+ Bool entryFound = false;
|
|
|
+ DynamicArray<ImageCacheEntry>& cache = m_assetsWindow.m_imageCache;
|
|
|
+ for(ImageCacheEntry& entry : cache)
|
|
|
+ {
|
|
|
+ if(entry.m_image->getUuid() == img->getUuid())
|
|
|
+ {
|
|
|
+ entry.m_lastSeenInFrame = crntFrame;
|
|
|
+ entryFound = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if(!entryFound)
|
|
|
+ {
|
|
|
+ cache.emplaceBack(ImageCacheEntry{img, crntFrame});
|
|
|
+ }
|
|
|
+
|
|
|
+ // Try to remove stale entries
|
|
|
+ const U32 frameInactivityCount = 60 * 30; // ~30"
|
|
|
+ while(true)
|
|
|
+ {
|
|
|
+ Bool foundStaleEntry = false;
|
|
|
+ for(auto it = cache.getBegin(); it != cache.getEnd(); ++it)
|
|
|
+ {
|
|
|
+ ANKI_ASSERT(crntFrame >= it->m_lastSeenInFrame);
|
|
|
+ if(crntFrame - it->m_lastSeenInFrame > frameInactivityCount)
|
|
|
+ {
|
|
|
+ cache.erase(it);
|
|
|
+ foundStaleEntry = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if(!foundStaleEntry)
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
} // end namespace anki
|