Skip to content
Search

ImGui

lastmod 2026-11-02created 2016-10-2642 edits

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;
}

Korean (Hangul) input

Add a Unicode Korean font to the ImGuiIO object:

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());

Even if you do not use an ImGui manager, just call it in the part where you initialize ImGui

yourself. For the font, I used Microsoft Malgun Gothic, the most common one.

io.Fonts->GetGlyphRangesKorean() specifies the particular glyph ranges needed for Korean.

(u8"한글 출력", i.e. "Korean output"):

    #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

Prefix the Korean strings with u8 to mark them explicitly as UTF-8 (Unicode) characters.

I suspect it uses MultiBytetoWideChar() internally.

That is how simply you can output Korean text in imgui.

Displaying a widget at the bottom of a window

#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();
}

Customizing the window title bar

  • ImGuiInputTextFlags_CallbackCompletion - A callback related to autocompletion. For example, the autocompletion callback is called when the user presses a specific key (usually Tab).
  • ImGuiInputTextFlags_CallbackHistory - A callback called when navigating input history. Typically used to select history entries when the up/down arrow keys are pressed.
  • ImGuiInputTextFlags_CallbackAlways - A callback called every time the text field is edited. Useful when you want the callback to keep running whatever change occurs.
  • ImGuiInputTextFlags_CallbackCharFilter - A callback for filtering input characters. It is called when the user types a character, letting you filter or modify the entered character.
  • ImGuiInputTextFlags_CallbackResize - A callback called when the size of the string changes. Mainly used when dynamic string memory allocation is needed, and useful for variable-length strings such as std::string.
  • ImGuiInputTextFlags_CallbackEdit - A callback called when the text is edited, only when an edit actually occurs.

HTML Rendering

Dynamically reserving the needed height at the bottom of a window

imgui keeps scrolling as content grows. So, as in the figure below, a method of creating a separate scrolling region with a Child and padding its height relative to the bottom of the window

Imgui-footer_height_to_reserve.png

Below is the code at around line 7242 of imgui_demo.cpp.

//-----------------------------------------------------------------------------
// [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();
// ...

Implementing double-click on 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();
}

Calculating the Size of the triangle button on the right of a Combo

Check the "BeginCombo" function in imgui_widgets.cpp:

Find the code that calculates the Size by looking around ImGuiComboFlags_NoArrowButton.

bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags)
{
    // ...

    const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
    const float preview_width = ((flags & ImGuiComboFlags_WidthFitPreview) && (preview_value != NULL)) ? CalcTextSize(preview_value, NULL, true).x : 0.0f;
    const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : ((flags & ImGuiComboFlags_WidthFitPreview) ? (arrow_size + preview_width + style.FramePadding.x * 2.0f) : CalcItemWidth());
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
    const ImRect total_bb(bb.Min, bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
    ItemSize(total_bb, style.FramePadding.y);
    if (!ItemAdd(total_bb, id, &bb))
        return false;

    // ...

    // Render shape
    const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
    const float value_x2 = ImMax(bb.Min.x, bb.Max.x - arrow_size);
    RenderNavHighlight(bb, id);
    if (!(flags & ImGuiComboFlags_NoPreview))
        window->DrawList->AddRectFilled(bb.Min, ImVec2(value_x2, bb.Max.y), frame_col, style.FrameRounding, (flags & ImGuiComboFlags_NoArrowButton) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersLeft);
    if (!(flags & ImGuiComboFlags_NoArrowButton))
    {
        ImU32 bg_col = GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
        ImU32 text_col = GetColorU32(ImGuiCol_Text);
        window->DrawList->AddRectFilled(ImVec2(value_x2, bb.Min.y), bb.Max, bg_col, style.FrameRounding, (w <= arrow_size) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersRight);
        if (value_x2 + arrow_size - style.FramePadding.x <= bb.Max.x)
            RenderArrow(window->DrawList, ImVec2(value_x2 + style.FramePadding.y, bb.Min.y + style.FramePadding.y), text_col, ImGuiDir_Down, 1.0f);
    }

    // ...
}

GUI DPI Scale

The proper way to make a Custom Widget

Developer's recommendation:

  1. InvisibleButton()

A passerby's recommendation:

  1. Give an ID to your widget using PushID()/PopID(). This will solve your widget affecting other widgets.
  2. Use ItemAdd()/ItemSize().
  3. If your widget is interactable use SetActiveID()/ClearActiveID().

Changing the font scale

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.

How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application?

Q: How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application?
  • When io.WantCaptureMouse is set, you need to discard/hide the mouse inputs from your underlying application.
  • When io.WantCaptureKeyboard is set, you need to discard/hide the keyboard inputs from your underlying application.
  • When io.WantTextInput is set, you can notify your OS/engine to popup an on-screen keyboard, if available (e.g. on a mobile phone, or console OS).

Animated Title

If you change the title with ImGui::Begin("..."), the ID assigned in imgui.ini keeps changing. (Focus also keeps moving to that window.)

In this case, use ###. Use it like TitleName###WindowID.

See the ShowExampleAppWindowTitles() function in the imgui_demo.cpp file.

BeginViewportSideBar

You can use it if you want to make something like a "Status Bar" or "Tool Bar".

// [1] BeginViewportSideBar signature receives ImGuiViewport* viewport
//
//       You can change this: 
//                  ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)ImGui::GetMainViewport();
//       To this: 
//                  ImGuiViewport* viewport = ImGui::GetMainViewport();
//
//       But you can pass just NULL because main viewport is the default.
//
// [2] ImGui::BeginViewportSideBar uses ImGui::Begin so the call to ImGui::End should be out of the {if scope}
//
// Updated version:

ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
float height = ImGui::GetFrameHeight();

if (ImGui::BeginViewportSideBar("##SecondaryMenuBar", NULL, ImGuiDir_Up, height, window_flags)) {
    if (ImGui::BeginMenuBar()) {
        ImGui::Text("Happy secondary menu bar");
        ImGui::EndMenuBar();
    }
}
ImGui::End();

if (ImGui::BeginViewportSideBar("##MainStatusBar", NULL, ImGuiDir_Down, height, window_flags)) {
    if (ImGui::BeginMenuBar()) {
        ImGui::Text("Happy status bar");
        ImGui::EndMenuBar();
    }
}
ImGui::End();

DockBuilder Example

ImGui::DockBuilderRemoveNode(dockspace_id); // Clear out existing layout
ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_Dockspace); // Add empty node
ImGui::DockBuilderSetNodeSize(dockspace_id, dockspace_size);

ImGuiID dock_main_id = dockspace_id; // This variable will track the document node, however we are not using it here as we aren't docking anything into it.
ImGuiID dock_id_prop = ImGui::DockBuilderSplitNode(dock_main_id, ImGuiDir_Left, 0.20f, NULL, &dock_main_id);
ImGuiID dock_id_bottom = ImGui::DockBuilderSplitNode(dock_main_id, ImGuiDir_Down, 0.20f, NULL, &dock_main_id);

ImGui::DockBuilderDockWindow("Log", dock_id_bottom);
ImGui::DockBuilderDockWindow("Properties", dock_id_prop);
ImGui::DockBuilderDockWindow("Mesh", dock_id_prop);
ImGui::DockBuilderDockWindow("Extra", dock_id_prop);
ImGui::DockBuilderFinish(dockspace_id);

You must use ImGuiDockNodeFlags_Dockspace so that the empty part of the Parent remains after an empty Split.

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

Fix for when GuiBeginPopupModal using GuiOpenPopup in a MenuItem does not work properly:

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 ↩

author Zer0 sitemap