Skip to content

ImGui

Bloat-free Immediate Mode Graphical User interface for C++ with minimal dependencies.

Categories

Bindings

Extensions

Custom Render Example

raylib example:

void RenderRayGui()
{
    ImGui::EndFrame();
    ImGui::Render();
    ImDrawData * draw_data = ImGui::GetDrawData();
    assert(draw_data != nullptr);

    if (draw_data->CmdListsCount == 0) {
        return;
    }

    ImGuiIO & io = ImGui::GetIO();

    // Avoid rendering when minimized,
    // scale coordinates for retina displays
    // (screen coordinates != framebuffer coordinates)
    auto const FRAME_BUFFER_WIDTH  = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x);
    auto const FRAME_BUFFER_HEIGHT = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y);
    if (FRAME_BUFFER_WIDTH <= 0 || FRAME_BUFFER_HEIGHT <= 0) {
        return;
    }

    // Will project scissor/clipping rectangles into framebuffer space
    ImVec2 const CLIP_OFF = draw_data->DisplayPos;         // (0,0) unless using multi-viewports
    ImVec2 const CLIP_SCALE = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2)

    // TODO: Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled
    // TODO: Setup viewport covering draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize
    // TODO: Setup orthographic projection matrix cover draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize
    // TODO: Setup shader: vertex { float2 pos, float2 uv, u32 color }, fragment shader sample color from 1 texture, multiply by vertex color.

    // Render command lists
    for (int n = 0; n < draw_data->CmdListsCount; n++) {
        ImDrawList const * cmd_list   = draw_data->CmdLists[n];
        ImDrawVert const * vtx_buffer = cmd_list->VtxBuffer.Data;
        ImDrawIdx  const * idx_buffer = cmd_list->IdxBuffer.Data;

        for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) {
            ImDrawCmd const * pcmd = &cmd_list->CmdBuffer[cmd_i];
            if (pcmd->UserCallback) {
                pcmd->UserCallback(cmd_list, pcmd);
            } else {
                // The texture for the draw call is specified by pcmd->TextureId.
                // The vast majority of draw calls will use the imgui texture atlas, which value you have set yourself during initialization.
                //MyEngineBindTexture((MyTexture*)pcmd->TextureId);

                // We are using scissoring to clip some objects. All low-level graphics API should supports it.
                // - If your engine doesn't support scissoring yet, you may ignore this at first. You will get some small glitches
                //   (some elements visible outside their bounds) but you can fix that once everything else works!
                // - Clipping coordinates are provided in imgui coordinates space (from draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize)
                //   In a single viewport application, draw_data->DisplayPos will always be (0,0) and draw_data->DisplaySize will always be == io.DisplaySize.
                //   However, in the interest of supporting multi-viewport applications in the future (see 'viewport' branch on github),
                //   always subtract draw_data->DisplayPos from clipping bounds to convert them to your viewport space.
                // - Note that pcmd->ClipRect contains Min+Max bounds. Some graphics API may use Min+Max, other may use Min+Size (size being Max-Min)
                ImVec2 pos = draw_data->DisplayPos;
                //MyEngineScissor((int)(pcmd->ClipRect.x - pos.x), (int)(pcmd->ClipRect.y - pos.y), (int)(pcmd->ClipRect.z - pos.x), (int)(pcmd->ClipRect.w - pos.y));

                // Render 'pcmd->ElemCount/3' indexed triangles.
                // By default the indices ImDrawIdx are 16-bits, you can change them to 32-bits in imconfig.h if your engine doesn't support 16-bits indices.
                //MyEngineDrawIndexedTriangles(pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer, vtx_buffer);

                auto texture_id = (std::intptr_t)pcmd->TextureId;
                rlEnableTexture((unsigned int)texture_id);
                rlBegin(RL_TRIANGLES);

                assert(pcmd->ElemCount >= 3 && pcmd->ElemCount % 3 == 0);
                auto const TRIANGLE_COUNT = pcmd->ElemCount / 3;

                for (int i = 0; i < TRIANGLE_COUNT; ++i) {
                    auto const POINT0 = vtx_buffer[idx_buffer[(i*3)+0]];
                    auto const POINT1 = vtx_buffer[idx_buffer[(i*3)+1]];
                    auto const POINT2 = vtx_buffer[idx_buffer[(i*3)+2]];

                    auto const COLOR0 = GetColor(POINT0.col);
                    auto const COLOR1 = GetColor(POINT1.col);
                    auto const COLOR2 = GetColor(POINT2.col);

                    rlColor4ub(COLOR0.r, COLOR0.g, COLOR0.b, COLOR0.a);
                    rlTexCoord2f(POINT0.uv.x, POINT0.uv.y);
                    rlVertex2f(POINT0.pos.x, POINT0.pos.y);

                    rlColor4ub(COLOR1.r, COLOR1.g, COLOR1.b, COLOR1.a);
                    rlTexCoord2f(POINT1.uv.x, POINT1.uv.y);
                    rlVertex2f(POINT1.pos.x, POINT1.pos.y);

                    rlColor4ub(COLOR2.r, COLOR2.g, COLOR2.b, COLOR2.a);
                    rlTexCoord2f(POINT2.uv.x, POINT2.uv.y);
                    rlVertex2f(POINT2.pos.x, POINT2.pos.y);
                }
                rlEnd();
                rlDisableTexture();
            }
            idx_buffer += pcmd->ElemCount;
        }
    }
}

imgui-sfml

ImGui binding for use with SFML.

Simple example

#include <imgui/imgui.h>
#include <imgui/imgui-sfml.h>

#include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/Graphics/CircleShape.hpp>
#include <SFML/System/Clock.hpp>
#include <SFML/Window/Event.hpp>

int runDefaultImGuiSfml()
{
    sf::RenderWindow window(sf::VideoMode(200, 200), LIBWORLD_MAIN_TITLE);
    window.setVerticalSyncEnabled(true);
    ImGui::SFML::Init(window);

    sf::Clock deltaClock;
    while (window.isOpen()) {
        sf::Event event;

        while (window.pollEvent(event)) {
            ImGui::SFML::ProcessEvent(event);

            if (event.type == sf::Event::Closed) {
                window.close();
            }
        }

        ImGui::SFML::Update(deltaClock.restart());

        ImGui::Begin(LIBWORLD_MAIN_TITLE); // begin window
        ImGui::End(); // end window

        window.clear();
        ImGui::Render();
        window.display();
    }

    ImGui::SFML::Shutdown();

    return 0;
}

한글 입력

ImGuiIO 객체의 유니코드 한글 폰트를 추가한다:

ImGuiManager::ImGuiManager()
{
    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGui::StyleColorsDark();
    ImGuiIO& io = ImGui::GetIO();
    io.Fonts->AddFontFromFileTTF("C:\\Windows\\Fonts\\malgun.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesKorean());
}

ImGuiManager::~ImGuiManager()
{
    ImGui::DestroyContext();
}
ImGuiIO& io = ImGui::GetIO();
io.Fonts->AddFontFromFileTTF("C:\\Windows\\Fonts\\malgun.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesKorean());

굳이 ImGui 매니져를 사용하지 않더라도, 본인이 ImGui를 초기화하는 부분에서

호출해주면 된다. 나는 폰트는 제일 흔한 마이크로소프트 맑은 고딕을 사용했다.

io.Fonts->GetGlyphRangesKorean()로 한국어의 필요한 특정 그리프 범위를 지정해준다.

(u8"한글 출력"):

    #pragma region IMGUI INTERFACE
    if (ImGui::Begin("Chatting Box"))
    {

        ImGui::TextColored(color,isConnect.c_str());
        //연결이 안되어 있으면 연결 재 시도
        if (!m_Net.m_bConnect)
        {
            if (ImGui::Button(u8"연결 재시도"))
            {
                if (m_Net.Connect(g_hWnd, SOCK_STREAM, 10000, IP_DD))
                {
                    m_bConnect = true;
                }
            }
        }
        else
        {
            ImGui::BeginChild(u8"채팅창", ImVec2(0, -ImGui::GetItemsLineHeightWithSpacing() - 15));
            ImGui::Text(chatItems);
            ImGui::EndChild();
            ImGui::Dummy(ImVec2(0.0f, 5));
            ImGui::InputText("", buffer, sizeof(buffer));
            ImGui::SameLine();
            if (ImGui::Button("Send"))
            {
                char clear[MAX_PATH] = { 0, };
                KPacket kPacket(PACKET_CHAT_MSG);
                kPacket << 123 << "Test" << (short)12 << buffer;

                //리턴 값이 0보다 작으면 전송되지 않았음
                if (m_Net.SendMsg(m_Net.m_Sock, kPacket.m_uPacket) < 0)
                {
                    ZeroMemory(&chatItems, sizeof(char) * 2048);
                    strcat(chatItems, "Error\n");
                    m_Net.m_bConnect = false;
                }

                strcpy(buffer, clear);
            }
        }
    }
    ImGui::End();
#pragma endregion

한글 부분에 u8를 붙여서 UTF-8 (유니코드) 글자임을 명시한다.

아마도 내부적으로 MultiBytetoWideChar()를 쓰고 있지 않을까 싶다.

이렇게 간단하게 imgui에서 한글 출력을 할 수가 있다.

윈도우 하단에 위젯 출력

#include "imgui.h"

void ExampleWindowWithBottomTextInput()
{
    // 입력 버퍼를 정의합니다.
    static char inputBuffer[128] = "";

    // 새로운 창을 생성합니다.
    ImGui::Begin("Example Window");

    // 창의 크기를 가져옵니다.
    ImVec2 windowSize = ImGui::GetWindowSize();

    // 현재 커서의 위치를 가져옵니다.
    ImVec2 cursorPos = ImGui::GetCursorPos();

    // 텍스트 입력 필드의 높이를 설정합니다.
    float inputFieldHeight = ImGui::GetTextLineHeightWithSpacing() + ImGui::GetStyle().FramePadding.y * 2;

    // 창의 하단에 텍스트 입력 필드를 배치하기 위한 위치를 계산합니다.
    float yOffset = windowSize.y - inputFieldHeight - ImGui::GetStyle().WindowPadding.y * 2;

    // 텍스트 입력 필드의 위치를 창의 하단으로 설정합니다.
    ImGui::SetCursorPosY(yOffset);

    // 텍스트 입력 필드를 추가합니다.
    ImGui::InputText("##input", inputBuffer, IM_ARRAYSIZE(inputBuffer));

    // 다시 커서를 원래 위치로 이동시킵니다.
    ImGui::SetCursorPos(cursorPos);

    // 추가적인 내용이 있을 수 있으므로 창을 끝냅니다.
    ImGui::End();
}

윈도우 타이틀바 커스터마이징

InputText 의 Callback 관련 플래그 목록

  • ImGuiInputTextFlags_CallbackCompletion - 자동 완성 기능과 관련된 콜백입니다. 예를 들어, 사용자가 특정 키(주로 Tab 키)를 누르면 자동 완성 콜백이 호출됩니다.
  • ImGuiInputTextFlags_CallbackHistory - 입력 히스토리 탐색 시 호출되는 콜백입니다. 주로 위/아래 화살표 키를 눌렀을 때 히스토리 항목을 선택하도록 합니다.
  • ImGuiInputTextFlags_CallbackAlways - 텍스트 필드가 편집될 때마다 호출되는 콜백입니다. 어떤 변경이 발생하든지 콜백이 계속해서 실행되도록 설정할 때 유용합니다.
  • ImGuiInputTextFlags_CallbackCharFilter - 입력 문자 필터링을 위한 콜백입니다. 사용자가 문자를 입력할 때 해당 콜백이 호출되어 입력된 문자를 필터링하거나 수정할 수 있습니다.
  • ImGuiInputTextFlags_CallbackResize - 문자열의 크기가 변경될 때 호출되는 콜백입니다. 주로 동적 문자열 메모리 할당이 필요한 경우에 사용되며, std::string과 같은 가변 길이 문자열에 유용합니다.
  • ImGuiInputTextFlags_CallbackEdit - 텍스트가 편집될 때 호출되는 콜백으로, 편집이 실제로 발생했을 때만 호출됩니다.

HTML Rendering

윈도우 하단에 필요한 만큼 높이 남기고 Dynamic 하게 조정하기

imgui는 컨텐츠가 늘어나면 계속 스크롤되는 특징이 있다. 그래서 아래 그림처럼 Child 로 스크롤 영역 따로 만들고 높이를 윈도우의 하단 기준으로 Padding 시키는 방법

Imgui-footer_height_to_reserve.png

아래는 imgui_demo.cpp의 약 7242 라인에 있는 코드 이다.

//-----------------------------------------------------------------------------
// [SECTION] Example App: Debug Console / ShowExampleAppConsole()
//-----------------------------------------------------------------------------

// Demonstrate creating a simple console window, with scrolling, filtering, completion and history.
// For the console example, we are using a more C++ like approach of declaring a class to hold both data and functions.
struct ExampleAppConsole
{
// ...
    void    Draw(const char* title, bool* p_open)
    {
// ...
        // Reserve enough left-over height for 1 separator + 1 input text
        const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing();
        if (ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), ImGuiChildFlags_None, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NavFlattened))
        {
// ...
        }
        ImGui::EndChild();
        ImGui::Separator();
// ...

Selectable 더블클릭 구현

if (ImGui::TreeNode("Basic"))
{
    static bool selection[5] = { false, true, false, false };
    ImGui::Selectable("1. I am selectable", &selection[0]);
    ImGui::Selectable("2. I am selectable", &selection[1]);
    ImGui::Selectable("3. I am selectable", &selection[2]);
    if (ImGui::Selectable("4. I am double clickable", selection[3], ImGuiSelectableFlags_AllowDoubleClick))
        if (ImGui::IsMouseDoubleClicked(0))
            selection[3] = !selection[3];
    ImGui::TreePop();
}

폰트 스케일 변경

As a dubious workaround, you can get your current ImFont*, change the font->Scale value and then call PushFont() on this font. After you call PopFont you want to restore the size.

You can also use SetWindowFontScale() which is a stupidly old API (that will be removed by an eventual PushFontSize/PopFontSize()`.

I won't add the explicit API until font sizes are handled more correctly at the atlas rasterizing level.

Animated Title

ImGui::Begin("...")으로 타이틀을 바꾸면 imgui.ini에 할당되는 ID가 자꾸 바뀐다. (계속 포커스도 해당 윈도우로 옮겨진다)

이 때 ### 를 사용하면 된다. 타이틀명###윈도우ID 처럼 사용하면 된다.

imgui_demo.cpp 파일의 ShowExampleAppWindowTitles() 함수를 확인하면 된다.

APIs

BeginChild

Begin a scrolling region.

sizing of child region allows for three modes:

  • 0.0 - use remaining window size
  • >0.0 - fixed size
  • <0.0 - use remaining window size minus abs(size)

Troubleshooting

MenuItem에서 GuiOpenPopup를 사용한 GuiBeginPopupModal가 정상작동하지 않을 경우 해결 방법:

bool openMyPopup = false;
if (ImGui::BeginPopupContextItem("BeginPopupContextItem")) {
    if (ImGui::MenuItem("Rename")) {
        openMyPopup = true;
    }
    ImGui::EndPopup(); // <--- remember this
}
if (openMyPopup) {
    ImGui::OpenPopup("OpenPopup");
}
if (ImGui::BeginPopupModal("OpenPopup")) {
    // ...
    ImGui::EndPopup();
}

Local Download

ImGui 1.49 release
Imgui-1.49.tar.gz
ImGui + SFML b764eb7
Imgui-sfml-b764eb7.zip

See also

Favorite site

Node Graph Editors

References


  1. Creating_awesome_GUI_for_you_game_dev_tools_with_ImGui_and_SFML-Part_1.pdf