Dear ImGui FAQ

From GiderosMobile
Revision as of 19:56, 3 October 2024 by MoKaLux (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Labels and the ID Stack

Dear ImGui internally need to uniquely identify UI elements. Elements that are typically not clickable (such as calls to the Text functions) don't need an ID. Interactive widgets (such as calls to Button buttons) need a unique ID. Unique ID are used internally to track active widgets and occasionally associate state to widgets. Unique ID are implicitly built from the hash of multiple elements that identify the "path" to the UI element.

Unique ID are often derived from a string label and at minimum scoped within their host window:

Begin("MyWindow");
Button("OK");          // Label = "OK",     ID = hash of ("MyWindow", "OK")
Button("Cancel");      // Label = "Cancel", ID = hash of ("MyWindow", "Cancel")
End();

Other elements such as tree nodes, etc. also pushes to the ID stack:

Begin("MyWindow");
if (TreeNode("MyTreeNode"))
{
    Button("OK");      // Label = "OK",     ID = hash of ("MyWindow", "MyTreeNode", "OK")
    TreePop();
}
End();

Two items labeled "OK" in different windows or different tree locations won't collide:

    Begin("MyFirstWindow");
    Button("OK");          // Label = "OK",     ID = hash of ("MyFirstWindow", "OK")
    End();
    Begin("MyOtherWindow");
    Button("OK");          // Label = "OK",     ID = hash of ("MyOtherWindow", "OK")
    End();

If you have a same ID twice in the same location, you'll have a conflict:

Button("OK");
Button("OK");          // ID collision! Interacting with either button will trigger the first one.

Fear not! this is easy to solve and there are many ways to solve it!

Solving ID conflict in a simple/local context: When passing a label you can optionally specify extra ID information within string itself. Use "##" to pass a complement to the ID that won't be visible to the end-user. This helps solving the simple collision cases when you know e.g. at compilation time which items are going to be created:

Begin("MyWindow");
Button("Play");        // Label = "Play",   ID = hash of ("MyWindow", "Play")
Button("Play##foo1");  // Label = "Play",   ID = hash of ("MyWindow", "Play##foo1")  // Different from above
Button("Play##foo2");  // Label = "Play",   ID = hash of ("MyWindow", "Play##foo2")  // Different from above
End();

If you want to completely hide the label, but still need an ID:

Checkbox("##On", &b);  // Label = "",       ID = hash of (..., "##On")   // No visible label, just a checkbox!

Occasionally/rarely you might want change a label while preserving a constant ID. This allows you to animate labels. For example you may want to include varying information in a window title bar, but windows are uniquely identified by their ID. Use "###" to pass a label that isn't part of ID:

Button("Hello###ID");  // Label = "Hello",  ID = hash of (..., "###ID")
Button("World###ID");  // Label = "World",  ID = hash of (..., "###ID")  // Same as above, even if the label looks different

sprintf(buf, "My game (%f FPS)###MyGame", fps);
Begin(buf);            // Variable title,   ID = hash of "MyGame"

Solving ID conflict in a more general manner: Use PushID() / PopID() to create scopes and manipulate the ID stack, as to avoid ID conflicts within the same window. This is the most convenient way of distinguishing ID when iterating and creating many UI elements programmatically. You can push a pointer, a string or an integer value into the ID stack. Remember that ID are formed from the concatenation of everything pushed into the ID stack. At each level of the stack we store the seed used for items at this level of the ID stack.

Begin("Window");
for (int i = 0; i < 100; i++)
{
  PushID(i);           // Push i to the id tack
  Button("Click");     // Label = "Click",  ID = hash of ("Window", i, "Click")
  PopID();
}
for (int i = 0; i < 100; i++)
{
  MyObject* obj = Objects[i];
  PushID(obj);
  Button("Click");     // Label = "Click",  ID = hash of ("Window", obj pointer, "Click")
  PopID();
}
for (int i = 0; i < 100; i++)
{
  MyObject* obj = Objects[i];
  PushID(obj->Name);
  Button("Click");     // Label = "Click",  ID = hash of ("Window", obj->Name, "Click")
  PopID();
}
End();

You can stack multiple prefixes into the ID stack:

Button("Click");       // Label = "Click",  ID = hash of (..., "Click")
PushID("node");
  Button("Click");     // Label = "Click",  ID = hash of (..., "node", "Click")
  PushID(my_ptr);
    Button("Click");   // Label = "Click",  ID = hash of (..., "node", my_ptr, "Click")
  PopID();
PopID();

Tree nodes implicitly creates a scope for you by calling PushID().

Button("Click");       // Label = "Click",  ID = hash of (..., "Click")
if (TreeNode("node"))  // <-- this function call will do a PushID() for you (unless instructed not to, with a special flag)
{
  Button("Click");     // Label = "Click",  ID = hash of (..., "node", "Click")
  TreePop();
}

When working with trees, ID are used to preserve the open/close state of each tree node. Depending on your use cases you may want to use strings, indices or pointers as ID. e.g. when following a single pointer that may change over time, using a static string as ID will preserve your node open/closed state when the targeted object change. e.g. when displaying a list of objects, using indices or pointers as ID will preserve the node open/closed state differently. See what makes more sense in your situation!

note: we used "..." above to signify whatever was already pushed to the ID stack previously

Integration

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

You can read the io.WantCaptureMouse, io.WantCaptureKeyboard and io.WantTextInput flags from the ImGuiIO structure.

e.g. if (ImGui::GetIO().WantCaptureMouse) { ... }

When io.WantCaptureMouse is set, imgui wants to use your mouse state, and you may want to discard/hide the inputs from the rest of your application.
When io.WantCaptureKeyboard is set, imgui wants to use your keyboard state, and you may want to discard/hide the inputs from the rest of your application.
When io.WantTextInput is set to may want to notify your OS to popup an on-screen keyboard, if available (e.g. on a mobile phone, or console OS).

Note: You should always pass your mouse/keyboard inputs to Dear ImGui, even when the io.WantCaptureXXX flag are set false. This is because imgui needs to detect that you clicked in the void to unfocus its own windows.

Note: The io.WantCaptureMouse is more correct that any manual attempt to "check if the mouse is hovering a window" (don't do that!). It handle mouse dragging correctly (both dragging that started over your application or over a Dear ImGui window) and handle e.g. popup and modal windows blocking inputs.

Note: Those flags are updated by ImGui::NewFrame(). However it is generally more correct and easier that you poll flags from the previous frame, then submit your inputs, then call NewFrame(). If you attempt to do the opposite (which is generally harder) you are likely going to submit your inputs after NewFrame(), and therefore too late.

Note: If you are using a touch device, you may find use for an early call to UpdateHoveredWindowAndCaptureFlags() to correctly dispatch your initial touch. We will work on better out-of-the-box touch support in the future.

Note: Text input widget releases focus on the "KeyDown" event of the Return key, so the subsequent "KeyUp" event that your application receive will typically have io.WantCaptureKeyboard == false. Depending on your application logic it may or not be inconvenient to receive that KeyUp event. You might want to track which key-downs were targeted for Dear ImGui, e.g. with an array of bool, and filter out the corresponding key-ups.)

Custom Shapes

Q: How can I display custom shapes? (using low-level ImDrawList API)

You can use the low-level ImDrawList api to render shapes within a window.

ImGui::Begin("My shapes");

ImDrawList* draw_list = ImGui::GetWindowDrawList();

// Get the current ImGui cursor position
ImVec2 p = ImGui::GetCursorScreenPos();

// Draw a red circle
draw_list->AddCircleFilled(ImVec2(p.x + 50, p.y + 50), 30.0f, IM_COL32(255, 0, 0, 255), 16);

// Draw a 3 pixel thick yellow line
draw_list->AddLine(ImVec2(p.x, p.y), ImVec2(p.x + 100.0f, p.y + 100.0f), IM_COL32(255, 255, 0, 255), 3.0f);

// Advance the ImGui cursor to claim space in the window (otherwise the window will appears small and needs to be resized)
ImGui::Dummy(ImVec2(200, 200));

ImGui::End();

ImDrawList usage

Refer to "Demo > Examples > Custom Rendering" in the demo window and read the code of ShowExampleAppCustomRendering() in imgui_demo.cpp from more examples.
To generate colors: you can use the macro IM_COL32(255,255,255,255) to generate them at compile time, or use ImGui::GetColorU32(IM_COL32(255,255,255,255)) or ImGui::GetColorU32(ImVec4(1.0f,1.0f,1.0f,1.0f)) to generate a color that is multiplied by the current value of style.Alpha.
Math operators: if you have setup IM_VEC2_CLASS_EXTRA in imconfig.h to bind your own math types, you can use your own math types and their natural operators instead of ImVec2. ImVec2 by default doesn't export any math operators in the public API. You may use #define IMGUI_DEFINE_MATH_OPERATORS #include "imgui_internal.h" to use the internally defined math operators, but instead prefer using your own math library and set it up in imconfig.h.
You can use ImGui::GetBackgroundDrawList() or ImGui::GetForegroundDrawList() to access draw lists which will be displayed behind and over every other dear imgui windows (one bg/fg drawlist per viewport). This is very convenient if you need to quickly display something on the screen that is not associated to a dear imgui window.
You can also create your own dummy window and draw inside it. Call Begin() with the NoBackground | NoDecoration | NoSavedSettings | NoInputs flags (The ImGuiWindowFlags_NoDecoration flag itself is a shortcut for NoTitleBar | NoResize | NoScrollbar | NoCollapse). Then you can retrieve the ImDrawList* via GetWindowDrawList() and draw to it in any way you like.
You can create your own ImDrawList instance. You'll need to initialize them with ImGui::GetDrawListSharedData(), or create your own instancing ImDrawListSharedData, and then call your renderer function with your own ImDrawList or ImDrawData data.


Dear ImGui