Hello, welcome to my second article about ImGui! This article is a collection of useful things, hacks and other stuff I’ve found while using ImGui. Mostly it’s focused on using modern C++ and some parts of STL with ImGui.

If you use SFML and want to learn how to use it with ImGui, use my binding and refer to this tutorial about setting it up!

ImGui widgets

Different ImGui widgets (taken from ImGui’s github page)

Contents


Introduction

If you didn’t read the first article, here’s a short recap to get you started.

Dear ImGui is an amazing library which can be used to create fast GUI for your game dev (and not only!) tools in very short amount of time. The resulting code is very simple to use and modify, you don’t have to store GUI widgets or register callbacks. Here’s some stuff that I’ve made in ImGui for my game:

Level editor

Level Editor


Animation editor

Animation editor

Okay, enough of intros, let’s get right to tips.

ImGui demo window

First of all, it’s very useful to check out ImGui test window and its source to see how various things can be done. You have to add imgui_demo.cpp to your build and then call ImGui::ShowTestWindow to show the window. Because there are so many examples, I won’t go over them and just focus on other stuff.

Labels

Labels are used in ImGui as unique IDs for widgets. You shouldn’t use same labels for two different widgets as it will introduce collisions between widgets and that will lead to some unwanted behavior.

Suppose you have two buttons with label “Meow” and you have a code like this:

if (ImGui::Button("Meow")) {
    std::cout << "Meow\n";
}
 
if (ImGui::Button("Meow")) {
    std::cout << "Purr\n";
}

The first button works as expected, but the second doesn’t work at all! These are the things that can happen when collisions occur between IDs. This won’t happen most of the time, after all there’s mostly no need to place two buttons which say the same thing in one window. But what if you really need these two “Meow” buttons? The solution is simple: you just have to add “##” and some stuff after that to resolve the collision:

if (ImGui::Button("Meow")) {
    std::cout << "Meow\n";
}
 
if (ImGui::Button("Meow##Second")) {
    std::cout << "Purr\n";
}

All the text after “##” is not displayed and only used to give unique IDs to widgets with same labels. IDs should be unique in the same scope, so it’s okay to have two widgets with the same label in two different windows or have one of them in some tree or list (trees, lists and some other widgets have their own scopes, so collisions won’t happen between items in them and other items).

Let’s look at another situation. Suppose you have an array of ints:

std::array<int, 10> arr = { 0 };

And now you want to create a bunch of InputInt widgets for each array element. ImGui::PushID/PopID come to the rescue! You can push ints, const char* or void* as IDs which will be appended to the label of the next created widget (but won’t be shown). For example, you can do this:

for (int i = 0; i < arr.size(); ++i) {
    ImGui::PushID(i);
    ImGui::InputInt("##", &arr[i]);
    ImGui::PopID();
}

There are some situations where you don’t have an int which you can use as part of ID, for example if you want to use for-ranged loop with the std::array. In that case, you can use pointers to elements of the array which will be unique:

for (auto& elem : arr) {
    ImGui::PushID(&elem);
    ImGui::InputInt("##", &elem);
    ImGui::PopID();
}

Getting back to the context of the window, tree, etc.

Suppose that you need to add some stuff to the window you’ve created before but you already called ImGui::End. No problem, just call ImGui::Begin with the name of the window in which you want to append stuff. Here’s an example:

ImGui::Begin("First window"); // begin first window
// some stuff
ImGui::End();
 
ImGui::Begin("Another window"); // begin second window
// some another stuff
ImGui::End();
 
// oops, forgot to add some stuff!
// let's go back to the context of the first window
ImGui::Begin("First window");
// forgotten stuff
ImGui::End();

InputFloatN + struct

Sometimes it’s useful to use InputFloat2 or InputFloat4 with your point or rectangle structs which can be defined like this:

struct Point {
    float x;
    float y;
};
 
struct Rect {
    float x;
    float y;
    float w;
    float h;
};

Using them with InputFloat2 or InputFloat4 is easy:

Point p{ 0.f, 0.f };
Rect r{ 0.f, 0.f, 0.f, 0.f };
ImGui::InputFloat2("Point", &p.x);
ImGui::InputFloat4("Rect", &r.x);

This works because both Point and Rect structs are POD and don’t have “holes” in them, so the data they store is contiguous and can be interpreted as array of floats.

This method is not very safe, of course, so use it at your own risk. Someone can modify the struct and this may break your code which assumed that the floats you want to modify are stored contiguously. Unfortunately, there’s no way to pass array of pointers to InputFloat2 or InputFloat4, but you can easily create your own solution. Let’s make a function which creates a widget similar to InputFloat4 and uses members of Rect struct explicitly:

namespace ImGui {
 
bool InputRect(const char* label, Rect* rectPtr,
    int decimal_precision = -1, ImGuiInputTextFlags extra_flags = 0)
{
    ImGui::PushID(label);
    ImGui::BeginGroup();
 
    bool valueChanged = false;
 
    std::array<float*, 4> arr = { &rectPtr->x, &rectPtr->y,
                                  &rectPtr->w, &rectPtr->h };
 
    for (auto& elem : arr) {
        ImGui::PushID(elem);
        ImGui::PushItemWidth(64.f);
        valueChanged |= ImGui::InputFloat("##arr", elem, 0, 0,
            decimal_precision, extra_flags);
        ImGui::PopID();
        ImGui::SameLine();
    }
 
    ImGui::SameLine();
    ImGui::TextUnformatted(label);
    ImGui::EndGroup();
 
    ImGui::PopID(); // pop label id;
 
    return valueChanged;
}

And now you can do this:

ImGui::InputRect("Rect", &r);

Using ImGui with STL

There are lots of things to be said about using ImGui with STL. ImGui doesn’t use STL at all and users have to pass raw arrays and const char*s instead of std::vectors and std::strings, so you can’t just use STL and some modern C++ right away, but it can be done with some work.

Arrays

Some widgets require you to use raw arrays but those are not the best because you can’t use them with algorithms, for ranged loops, etc. And the other problem is that you have to manage the memory of variable size arrays yourself using new and delete. Using std::array with Imgui::InputInt4 which expects you to pass raw array is easy, just do it like this:

std::array<int, 4> arr2 = { 0 };
ImGui::InputInt4("IntRect", arr2.data());

std::array::data returns a pointer to raw int array which can be passed to ImGui::InputInt4.

The same can be done with std::vectors which are guaranteed to be contiguous, so you can just use std::vector::data the same way:

std::vector<int> arr3(4, 0);
ImGui::InputInt4("IntRect", arr3.data());

ComboBox, ListBox

ComboBox and ListBox can be used with arrays of const chars, but what if you have std::vector<std::string> instead? You have to redefine callback which ImGui calls to get next item of the array. Here’s how you can write Combo and ListBox wrappers for std::vector<std::string>:

namespace ImGui
{
static auto vector_getter = [](void* vec, int idx, const char** out_text)
{
    auto& vector = *static_cast<std::vector<std::string>*>(vec);
    if (idx < 0 || idx >= static_cast<int>(vector.size())) { return false; }
    *out_text = vector.at(idx).c_str();
    return true;
};
 
bool Combo(const char* label, int* currIndex, std::vector<std::string>& values)
{
    if (values.empty()) { return false; }
    return Combo(label, currIndex, vector_getter,
        static_cast<void*>(&values), values.size());
}
 
bool ListBox(const char* label, int* currIndex, std::vector<std::string>& values)
{
    if (values.empty()) { return false; }
    return ListBox(label, currIndex, vector_getter,
        static_cast<void*>(&values), values.size());
}
 
}

Ugh, that’s not the prettiest code, but it works. We store pointer to our vector in void* and cast it back to vector in our lambda. What’s good about that code is that we don’t create any copies of strings and just use const char pointers stored in std::strings from our std::vector.

InputText and std::string

ImGui lets you pass char array in InputText and then it modifies it when user enters some text in the input field. The problem is that it’s hard to know the size of input in advance, so you have to allocate large enough buffer and then pass it in InputText.

We can use std::vector<char> as our buffer instead of using raw arrays. If the size of buffer is known at compile time, you can use std::array<char, N>, where N is the size of the buffer.

You can then create std::string from your buffer like this:

std::string str = arr.data();

One of std::string constructors takes const char* so we get it by calling data.

Be careful when using std::string as input buffer! Sure, you can always call std::string::resize and allocate enough space, but don't use it in string handling code, because the string may contain zeros or trash and this can lead to some nasty bugs. The size of the string may not be what you expect. Moreover, when ImGui changes std::string's internal char array, it doesn't modify it's size which can be another source of hard bugs.

Callbacks

ImGui lets you add callbacks to different widgets. Let’s make a silly callback which replaces all user input with char ‘A’.

First you define a free function:

int callback(ImGuiTextEditCallbackData* data)
{
    data->EventChar = 'A';
    return 0;
}

And then pass it to InputText:

ImGui::InputText("Text", textArr.data(), textArr.size(),
    ImGuiInputTextFlags_CallbackCharFilter, callback);

You can also use stateless lambdas which can be used just as free functions:

ImGui::InputText("Text", textArr.data(), textArr.size(),
    ImGuiInputTextFlags_CallbackCharFilter, 
    [](ImGuiTextEditCallbackData* data)
    {
        data->EventChar = 'A';
        return 0;
    }
);

Suppose we don’t want to hard-code replacement character and want to use a char variable instead. We can’t capture the state in our lambda (it can’t be used as a free function), so we store the pointer to char in ImGuiTextEditCallbackData::UserData:

const char replacementChar = 'A';
ImGui::InputText("Text2", textArr.data(), textArr.size(),
    ImGuiInputTextFlags_CallbackCharFilter,
    [](ImGuiTextEditCallbackData* data)
    {
        data->EventChar = *static_cast<const char*>(data->UserData);
        return 0;
    },
    const_cast<char*>(&replacementChar));

Not bad, but capturing the state is so much prettier! And what if you wanted to pass more than one variable? This would get much harder, so I’ll show you how to use lambdas with state with ImGui!

Let’s define our own function which will lets us pass lambdas with state:

namespace ImGui {
 
template <typename F>
bool InputTextCool(const char* label, char* buf, size_t buf_size,
    ImGuiInputTextFlags flags = 0,
    F callback = nullptr, void* user_data = nullptr)
{
    auto freeCallback = [](ImGuiTextEditCallbackData* data) {
        auto& f = *static_cast<F*>(data->UserData);
        return f(data);
    };
    return InputText(label, buf, buf_size, flags,
        freeCallback, &callback);
}
 
}

Here’s how it works: we create another lambda which is stateless and calls our original lambda which is stored in ImGuiTextEditCallbackData::UserData. And now we can pass lambda with state like this:

ImGui::InputTextCool("Text3", textArr.data(), textArr.size(),
    ImGuiInputTextFlags_CallbackCharFilter,
    [&replacementChar](ImGuiTextEditCallbackData* data)
    {
        data->EventChar = replacementChar;
        return 0;
    });

Nice! But we can do better, because if we want to do the same with other widgets, we have to do the same thing over and over again. So let’s generalize the approach. First of all, let’s create a function which returns stateless lambda from lambda with state:

template <typename F>
auto getFreeCallback(F f)
{
    return [](function_traits<F>::arg_t<0> data) {
        auto& f = *static_cast<F*>(data->UserData);
        return f(data);
    };
}

This is the same approach we’ve used in InputTextCool function. function_traits<F>::arg_t<0> gets us the first argument of passed lambda. These traits can be defined like this:

template <typename T>
struct function_traits
    : public function_traits<decltype(&T::operator())>
{};
 
template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const>
{
    template <size_t i>
    using arg_t = std::tuple_element_t<i, std::tuple<Args...>>;
};

And now we can do this:

auto replacementLambda = [&replacementChar](ImGuiTextEditCallbackData* data)
{
    data->EventChar = replacementChar;
    return 0;
};
 
ImGui::InputText("Text4", textArr.data(), textArr.size(),
    ImGuiInputTextFlags_CallbackCharFilter,
    getFreeCallback(replacementLambda), &replacementLambda);

We can do the same thing “inline” like this:

using ReplacementLambdaType = decltype(replacementLambda);
ImGui::InputText("Text5", textArr.data(), textArr.size(),
    ImGuiInputTextFlags_CallbackCharFilter,
    [](ImGuiTextEditCallbackData* data)
    {
        auto f = *static_cast<ReplacementLambdaType*>(data->UserData);
        return f(data);
    }, &replacementLambda);

Note that we had to store our lambda type in ReplacementLambdaType type alias to cast from void* later. This is almost the same as the previous approach (which I like more!).

Conclusion

ImGui is a wonderful library and it’s cool that you can easily expand it and use with all the stuff you like. There are some other tricks and tips I have to share, but this article got pretty big and so another part of this article will probably happen some day.

If you want to know about something or have something to share, feel free to do it in comments, I’ll use it for the next article. See ya!


Follow me on twitter @EliasDaler to not miss the new stuff!