ImGui Docking and Viewports

引言

上一节我们添加了 glm 并讨论了游戏的一般数学库。今天我们将做一些 ImGUI 调整并修复一些新功能。

目前我们的 ImGUI 还需添加 Docking 和 Viewports。Docking 就是 dock 窗口,比如在 VisualStudio 或者其他软件中,我们可以剥离某些窗口,可以将它们拖拽并放在某处。

ImGUI 实际上已经集成了这个功能,你可以像上面这样组织我们的窗口,然后在其中可以有多个选项卡,可以根据需要停靠它们。这非常重要,因为我希望引擎有一个关卡编辑器。

绑定视口也是它们制作的东西,我们可以将 ImGUI 的窗口拖动到应用程序外部,而我实际上只是即时创建新的窗口、新的渲染上下文以及所有内容。

我认为这两个功能确实有助于引擎,并且我们不需要像 WPF、QT 这样的 UI 框架,ImGUI 完全可以满足我们。

Docking 分支

显然,我们如果拖拽 ImGUI 到窗口外,它并没有走到外面。

打开 Github,我们将切换到 ImGUI 的 docking 分支。

打开命令行窗口:

cd '/e/Infinite Engine/Infinite/Infinite/vendor/imgui'
git status
git checkout docking
git status
git pull origin/docking

切换 imgui 至 docking 分支,与上游仓库保持同步。

git add imgui

预生成文件

回到 Infinite/premake5.lua1 让 imgui 包含于沙盒程序:

project "Sandbox"
location "Sandbox"
kind "ConsoleApp"
staticruntime "off"

language "C++"

targetdir ("bin/" .. outputdir .. "/%{prj.name}")
objdir ("bin-int/" .. outputdir .. "/%{prj.name}")

files
{
"%{prj.name}/src/**.h",
"%{prj.name}/src/**.cpp"
}

includedirs
{
"Infinite/vendor/spdlog/include",
"Infinite/src/Infinite",
"Infinite/src/Events",
"Infinite/src/**.h",
"Infinite/src/**.cpp",
+ "Infinite/vendor/imgui",
"%{IncludeDir.glm}"
}

links
{
"Infinite"
}

filter "system:windows"
cppdialect "C++17"
systemversion "latest"

defines
{
"IFN_PLATFORM_WINDOWS"
}

filter "configurations:Debug"
defines "IFN_DEBUG"
runtime "Debug"
symbols "On"

filter "configurations:Release"
defines "IFN_RELEASE"
runtime "Release"
optimize "On"

filter "configurations:Dist"
defines "IFN_DIST"
runtime "Release"
optimize "On"

ImGui 调整

新建一个新的 ImGuiBuild.cpp

#include "ifnpch.h"

#define IMGUI_IMPL_OPENGL_LOADER_GLAD
#include "../imgui/examples/imgui_impl_opengl3.cpp"
#include "../imgui/examples/imgui_impl_glfw.cpp"

引入之前 imgui 文件中的示例程序,相对的我们不再需要复杂的拷贝整个项目,所以删除 Platform/OpenGL 下的两个文件:

  • ImGuiOpenGLRenderer.cpp
  • ImGuiOpenGLRenderer.h

ImGuiLayer 调整

ImGuiLayer.h 头文件中我们取消了之前特定方法的定义;

#pragma once

#include "../Layer.h"

#include "../Events/KeyEvent.h"
#include "../Events/MouseEvent.h"
#include "../Events/ApplicationEvent.h"

namespace Infinite {

class INFINITE_API ImGuiLayer : public Layer
{
public:
ImGuiLayer();
~ImGuiLayer();

virtual void OnAttach() override;
virtual void OnDetach() override;
virtual void OnImGuiRender() override;

void Begin();
void End();

private:
float m_Time = 0.0f;

};
}

新加入三个方法:

  • void Begin()
  • void End()
  • virtual void OnImGuiRender()

ImGuiLayer.cpp 我们实现方法:

#include "ifnpch.h"
#include "ImGuiLayer.h"

#include "../imgui/imgui.h"
#include "../imgui/examples/imgui_impl_glfw.h"
#include "../imgui/examples/imgui_impl_opengl3.h"

// TEMPORARY
#include <GLFW/glfw3.h>
#include <glad/glad.h>

#include "../Application.h"

namespace Infinite {

ImGuiLayer::ImGuiLayer()
: Layer("ImGuiLayer")
{

}

ImGuiLayer::~ImGuiLayer()
{

}

void ImGuiLayer::OnAttach()
{
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
//io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows
//io.ConfigFlags |= ImGuiConfigFlags_ViewportsNoTaskBarIcons;
//io.ConfigFlags |= ImGuiConfigFlags_ViewportsNoMerge;

// Setup Dear ImGui style
ImGui::StyleColorsDark();
//ImGui::StyleColorsClassic();

// When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones.
ImGuiStyle& style = ImGui::GetStyle();
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
{
style.WindowRounding = 0.0f;
style.Colors[ImGuiCol_WindowBg].w = 1.0f;
}

Application& app = Application::Get();
GLFWwindow* window = static_cast<GLFWwindow*>(app.GetWindow().GetNativeWindow());

// Setup Platform/Renderer bindings
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init("#version 410");
}

void ImGuiLayer::OnDetach()
{
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
}

void ImGuiLayer::Begin()
{
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
}

void ImGuiLayer::End()
{
ImGuiIO& io = ImGui::GetIO();

Application& app = Application::Get();
io.DisplaySize = ImVec2(app.GetWindow().GetWidth(), app.GetWindow().GetHeight());

// Rendering
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());

if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
{
GLFWwindow* backup_current_context = glfwGetCurrentContext();
ImGui::UpdatePlatformWindows();
ImGui::RenderPlatformWindowsDefault();
glfwMakeContextCurrent(backup_current_context);
}
}

void ImGuiLayer::OnImGuiRender()
{
static bool show = true;
ImGui::ShowDemoWindow(&show);
}
}

LayerStack 调整

层级栈的调整比较简单,头文件 LayerStack.h 由普通的 int 变量替换迭代器:

#pragma once

#include "Core.h"
#include "Layer.h"

#include <vector>

namespace Infinite {

class INFINITE_API LayerStack
{
public:
LayerStack();
~LayerStack();

void PushLayer(Layer* layer);
void PushOverlay(Layer* layer);
void PopLayer(Layer* layer);
void PopOverlay(Layer* layer);

std::vector<Layer*>::iterator begin() { return m_Layers.begin(); }
std::vector<Layer*>::iterator end() { return m_Layers.end(); }

private:
std::vector<Layer*> m_Layers;
+ unsigned int m_LayerInsertIndex = 0;
};
}

LayerStack.cpp 同理:

#include "ifnpch.h"
#include "LayerStack.h"

namespace Infinite {

LayerStack::LayerStack()
{

}

LayerStack::~LayerStack()
{
for (Layer* layer : m_Layers)
delete layer;
}

void LayerStack::PushLayer(Layer* layer)
{
m_Layers.emplace(m_Layers.begin() + m_LayerInsertIndex, layer);
+ m_LayerInsertIndex++;
}

void LayerStack::PushOverlay(Layer* overlay)
{
m_Layers.emplace_back(overlay);
}

void LayerStack::PopLayer(Layer* layer)
{
auto it = std::find(m_Layers.begin(),m_Layers.end(),layer);
if (it != m_Layers.end())
{
m_Layers.erase(it);
+ m_LayerInsertIndex--;
}
}

void LayerStack::PopOverlay(Layer* overlay)
{
auto it = std::find(m_Layers.begin(),m_Layers.end(),overlay);
if (it != m_Layers.end())
m_Layers.erase(it);
}

}

调试

我们的 imgui 窗口终于可以拖拽到引擎之外!

但别高兴的太早,当我们在沙盒程序 SandboxApp.cpp 中重载 OnImGuiRender() 方法:

#include <Infinite.h>

#include "../imgui/imgui.h"

class ExampleLayer : public Infinite::Layer
{
public:
ExampleLayer()
: Layer("Example")
{

}

void OnUpdate() override
{
// IFN_INFO("ExampleLayer::Update");

if (Infinite::Input::IsKeyPressed(IFN_KEY_TAB))
IFN_INFO("Tab key is pressed (poll)!");
}


+ virtual void OnImGuiRender() override
+ {
+ ImGui::Begin("Test");
+ ImGui::Text("Hello World");
+ ImGui::End();
+ }

void OnEvent(Infinite::Event& event) override
{
if (event.GetEventType() == Infinite::EventType::KeyPressed)
{
Infinite::KeyPressedEvent& e = (Infinite::KeyPressedEvent&)event;
if (Infinite::Input::IsKeyPressed(IFN_KEY_TAB))
IFN_INFO("Tab key is pressed (event)!");

IFN_TRACE("{0}", (char)e.GetKeyCode());
}
}

};

class Sandbox : public Infinite::Application {
public:
Sandbox()
{
PushLayer(new ExampleLayer());
}
~Sandbox() {}
};

Infinite::Application* Infinite::CreateApplication() {
return new Sandbox();
}

再次运行项目,会出现 3 个外部符号解析的报错(事实上做到这里为了解决 3 个报错,我让错误变成了12 个,之后变成了 128 个……感谢 git 回滚版本)。

出错的原因在于动态链接库的导入导出有问题,这个我们会在接下来修复报错和警告。

补充:可以在 vendor/imgui/imconfig.h 中取消注释解决该问题:

-//#define IMGUI_API _declspec(dllexport)
+#define IMGUI_API _declspec(dllexport)