您的位置:

ImGui绘制实践

ImGui是一个为C++语言实现的、轻量、可信赖的、跨平台、功能丰富的用户界面框架,它为程序开发者提供了简单的工具使他们能为自己的程序添加图形用户界面。所有的控件都是通过硬编码的方式在一个OpenGL窗口渲染出来的,因此界面渲染效率非常高。同时,由于ImGui的实现非常简单清晰,因而开发者容易根据自己的需求扩展定制化的控件。ImGui只是一个跨平台的窗口、输入、图形绘制库,它不提供音频或者其他多媒体功能等其它与UI无关的功能。本篇文章中我们将对ImGui的绘制实践进行探讨。

一、基础实现

ImGui的核心要素是客户端和图形依赖对于的库,库的实现可以包括OpenGL、Direct3D、Vulkan等。在初始化之后,程序需要不断调用ImGui的渲染函数将界面渲染出来。我们可以通过以下代码实现最基础的ImGui界面:

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

// 初始化ImGui
ImGui::Initialize();
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init(glsl_version);

// 每一帧渲染ImGui
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
ImGui::ShowDemoWindow(); // 示例窗口
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());

在上述代码中,我们调用了ImGui的初始化函数并指定了使用OpenGL 3.0,同时实现了一个示例窗口。然后我们使用ImGui的渲染函数将绘制好的图形界面展现在屏幕上。注意,这里的ImGui实现依赖于glfw和OpenGL相关库。

二、添加控件实现

在实现了最基础的ImGui界面之后,我们可以来尝试一下添加一些常见的控件。

1. Button控件

我们可以通过ImGui::Button函数实现一个单行按钮,并为其添加一个事件回调函数。下面的代码显示了如何实现一个简单的Button控件:

ImGui::Begin("Button");
if (ImGui::Button("Click me!"))
    std::cout << "Button clicked!" << std::endl;
ImGui::End();

在上述代码中,我们使用ImGui::Button函数并指定按钮的名称为"Click me!"。再在按钮下方添加一个事件回调函数使得按钮被点击时会输出一条信息到控制台上。除了回调函数外,ImGui::Button函数还支持传递参数控制按钮的显示面积、颜色、对准方式等。

2. 滚动条控件

ImGui::Scrollbar函数可以实现一个可滚动的滚动条控件,我们可以通过其提供的回调函数参数来实现滚动事件的监听。下面的代码演示了如何实现一个可滚动的滑块滑动控件:

static float scroll_value = 0.5f; // 初始值
ImGui::Begin("Scrollbar");
ImGui::Scrollbar("scrollbar", ImGuiAxis_X, &scroll_value, 0.0f, 1.0f);
ImGui::End();

在上述代码中,我们使用ImGui::Scrollbar函数来绘制一个滑动条,并为其指定了方向和范围。通过&scroll_value可以获取当前滑动条位置,将其传递给Animator的Update()函数就可以实现相关的控制代码。

3. 标签页控件

ImGui::TabBar函数实现了一个标签页控件,我们可以通过其提供的回调函数参数实现标签页选择后的效果。下面是一个实现简单的标签页控件:

static int tab_index = 0;
ImGui::Begin("Tab bar");
if (ImGui::BeginTabBar("TabBar"))
{
    if (ImGui::TabItem("Tab1"))
        tab_index = 0;
    if (ImGui::TabItem("Tab2"))
        tab_index = 1;
    ImGui::EndTabBar();
}
ImGui::Text("Selected tab: %d", tab_index);
ImGui::End();

在上述代码中,我们使用ImGui::BeginTabBar和ImGui::TabItem函数分别为标签页控件中的标签栏和标签页添加标签。当某一个标签页被选中时,回调函数tab_index会得到被选中的标签页的序号。再通过实现相关的切换代码就可以实现标签页切换了。除此之外,我们还可以通过其他参数控制标签页的样式、颜色、尺寸等。

三、绘制自定义控件

ImGui的灵活性在于其可以轻松定制化各种控件来满足开发者的需求。本节将演示如何在ImGui中绘制一个自定义的动态图形控件。

1. 实现一个Animator类

动态图形控件需要一个Animator类来提供绘制相关的逻辑。下面的Animator类会在界面上绘制一个从一侧到另一侧移动的矩形:

class Animator
{
public:
    Animator() {}

    void Update(float delta_time)
    {
        float speed = 50.0f;
        m_position.x += speed * delta_time;
        if (m_position.x > 800.0f)
        {
            m_position.x = -50.0f;
        }
    }

    void Draw()
    {
        ImGui::GetWindowDrawList()->AddRectFilled(m_position, m_position + ImVec2(50.0f, 50.0f), IM_COL32(60, 60, 60, 255));
    }

private:
    ImVec2 m_position = ImVec2(-50.0f, 200.0f);
};

在Animator中,我们实现了Update和Draw两个函数。Update函数是通过delta_time计算动画对象的位置,Draw函数则将动态图形绘制到屏幕上。GetWindowDrawList()函数将返回ImGui提供的绘制列表控制器,我们可以在这个列表中添加我们需要绘制的所有的动态图形。

2. 使用Animator类绘制动态图形

我们可以在ImGui窗口的主循环中反复更新和绘制Animator控件:

Animator anim;
while (!glfwWindowShouldClose(window))
{
    glfwPollEvents();
    ImGui_ImplOpenGL3_NewFrame();
    ImGui_ImplGlfw_NewFrame();
    ImGui::NewFrame();

    // 动画控件更新
    float delta_time = 1.0f / ImGui::GetIO().Framerate;
    anim.Update(delta_time);

    // 绘制动画控件
    anim.Draw();

    // 将所有UI元素全部渲染
    ImGui::Render();
    ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());

    glfwSwapBuffers(window);
}

在上述代码中,我们在主循环中对Animator控件的位置进行更新,并将其绘制到ImGui绘制列表中。其中delta_time物理值为当前帧的时间与前一帧的时间之差,通过待绘制动图数据的时间与delta_time的区别来计算偏移量,从而实现运动效果。

四、结语

通过本文,我们已经可以初步了解ImGui绘制的实现流程,以及如何在其中添加、定制化各式各样的控件。当然,本文中只是演示了一些简单的用例,对于深度定制化的控件,我们还需通过ImGui本身提供的API函数,来实现各种更加丰富的功能表现。