ImGui¶
openpete_imgui.h gives a mod access to the ImGui C API (ImGui 1.92.8, through the C bindings dear_bindings generates).
- ImGui docs
- Generated bindings:
<exe>/sdk/dcimgui.h ImGui_ShowDemoWindow()draws a running demo of every widget
Names follow dear_bindings: ImGui::Begin is ImGui_Begin, a call passing every optional argument is the ...Ex form (ImGui_PlotLinesEx), an overload takes a type suffix (ImGui_ComboChar), and a member function takes its struct's prefix (ImDrawList_AddLine). A mod cannot take the address of an ImGui_* function, since the name is a macro over a table entry; use the entry (openpete_imgui_tbl->ImGui_Begin).
openpete_mod_ui.h stays the simple path: eleven widgets, no macro to add, and no ImGui version in the mod's build. Use this header when those eleven are not enough: windows, tables, plots, tree nodes, tabs, popups, colour editors, drag and input widgets, draw lists, fonts.
Glossary¶
| Term | Meaning |
|---|---|
| body | The function registered as the mod's UI, run by the engine inside its ImGui frame. Every ImGui_* call a mod makes is made here. |
| section | A mod's registered UI, drawn in the mod's node of the Mods panel. |
| always section | A section registered with OPENPETE_MOD_UI_ALWAYS, whose body runs at top level on every present instead of inside the mod's node. |
| table | The function-pointer table (openpete_imgui_tbl) every ImGui_* macro reads, one entry per callable ImGui function. |
| descriptor | The symbol OPENPETE_MOD_IMGUI() exports, which the loader checks against the engine's table before binding. |
| overlay | The dev overlay, opened with the M key. Its Mods panel gives each mod a node. |
| present | One drawn frame, as distinct from a tick, the 29.913 Hz simulation step. Presents per tick depend on the machine. |
Adding ImGui to a mod¶
Include the header and place the macro at file scope in exactly one source file of the mod:
#include <openpete_mod_api.h>
#include <openpete_mod_ui.h>
#include <openpete_imgui.h>
OPENPETE_MOD_IMGUI()
static void ui_draw(const openpete_mod_ui_t* ui) {
(void)ui;
if (ImGui_Begin("my window", NULL, 0)) {
ImGui_Text("hello from a mod");
}
ImGui_End();
}
OPENPETE_MOD_UI_SECTION(ui_draw)
OPENPETE_MOD_IMGUI() defines the function-pointer table every ImGui_* call reads and exports the descriptor the loader resolves when it opens the mod. A mod without the macro references no table and nothing is bound. Nothing is added to the mod's compile line and the mod still links no engine symbol: an ImGui_* name is a macro over one table entry.
The descriptor pins the ImGui version, the table size and a digest of the table's entries. A prebuilt object built against a different SDK is refused and its mod disabled, because a mod bound to a table whose slots have moved would call the wrong function on its first widget. A source mod is never refused: the SDK headers are part of the mod's build key, so an ImGui bump recompiles it at the next boot.
Binding is logged once per mod:
mods: <id>: ImGui bound (1.92.8, N entries)
Where the calls are legal¶
Inside a UI section body, and nowhere else. A body runs on the compose thread, inside the engine's ImGui frame, while the mod is enabled. A plain OPENPETE_MOD_UI_SECTION body runs in the mod's node of the Mods section, so only while the dev overlay is open; an always section (below) runs on every present.
A call from a present hook or a tick hook is dropped: those run outside the frame and on another thread, where ImGui's own checks are compiled out and the failure would be a null-window dereference. The call returns a zeroed value and the engine logs the mod and the function once:
mods: <id>: ImGui_Begin called outside the mod's UI section (from a present or tick hook) — dropped
A body draws and nothing else: guest access, game calls and override registration are refused inside one, as they are inside a present hook. Read guest RAM in a tick hook, copy what the panel needs into host memory, and let the body read the copy.
The body still receives the openpete_mod_ui_t* argument, so the vtable and the ImGui calls mix freely in one body. An ImGui_Begin inside the mod's node opens an independent top-level window, which is ImGui's documented behaviour.
Always-on sections¶
Register the body with the flags form to run it on every present instead of only inside the Mods panel:
OPENPETE_MOD_UI_SECTION_FLAGS(ui_draw, OPENPETE_MOD_UI_ALWAYS)
An always section keeps a stats window or a draw-list overlay on screen during play. The rules that change with it:
- The body owns its windows. It runs at top level, not inside a tree node (a node the player collapsed must not make a window disappear), so every widget emitted outside an
ImGui_Beginlands in ImGui's default debug window. Open a window and put the content in it. - No input while the overlay is closed. The game keeps the mouse and the keyboard, and the engine puts
ImGuiWindowFlags_NoInputson every window the body opens: no clicks, no nav, no hover.ui->overlay_open()returns 1 while the overlay is up, which is when widgets in an always window can be used; draw interactive widgets only then.ImGui_GetMousePosstill reports the game's cursor. - The pass is skipped while the in-game settings menu is open. The menu and the dev overlay are mutually exclusive input owners and the menu is drawn first, so an always window would paint over it.
ui_statuslines stay in the mod's node in the Mods section, where the rest of the mod's row is.
ImGui draws into the live present only, so an always window is never in a --record capture or a screenshot, and it is drawn on top of an OP_POSTFX_COMPOSITE chain rather than through it. A HUD that must appear in captures belongs in the draw service (draw_text), which is drawn pre-chain as content.
Calls not in the table¶
The table omits these, and the macros with them, so calling one fails the mod's compile with an undefined reference to the dcimgui.h declaration.
| Family | Calls |
|---|---|
| Context lifecycle | ImGui_CreateContext*, ImGui_DestroyContext*, ImGui_SetCurrentContext, ImGui_GetCurrentContext |
| Frame lifecycle | ImGui_NewFrame, ImGui_EndFrame, ImGui_Render, ImGui_GetDrawData |
| Allocator, settings and state storage | ImGui_SetAllocatorFunctions, ImGui_GetAllocatorFunctions, ImGui_LoadIniSettings*, ImGui_SaveIniSettings*, ImGui_SetStateStorage |
| Input injection and capture forcing | ImGuiIO_Add*Event, ImGuiIO_AddInputCharacter*, ImGuiIO_ClearInputKeys, ImGuiIO_ClearEventsQueue, ImGui_SetNextFrameWantCaptureMouse, ImGui_SetNextFrameWantCaptureKeyboard |
| Platform IO (clipboard, shell-open, renderer callbacks) | ImGui_GetPlatformIO |
| Font atlas mutation | ImFontAtlas_Clear*, ImFontAtlas_RemoveFont, ImFontAtlas_Build (ImFontAtlas_AddFont* stays) |
| Version check | ImGui_DebugCheckVersionAndDataLayout |
Error handling¶
ImGui reports unbalanced Begin/End, Push/Pop and similar issues as recoverable user errors. The engine brackets every body:
- the ImGui stack sizes are stored before the body and recovered after it, so a window left open does not swallow the panel or the frame;
ImGuiStyleand theImGuiIOfields a body can reach (IniFilename,LogFilename,ConfigFlags,UserData, and the backend user-data pointers) are snapshotted and put back;- ImGui's own assert and error tooltip are switched off for the body and the engine logs the error against the mod instead:
mods: <id>: ImGui error inside the mod's UI section: Missing End()
A body that errors on three consecutive presents is logged once more and then stops drawing for the rest of the session, so it cannot flood the log or keep drawing a broken section. Issues ImGui cannot recover from (a null ImDrawList*, a bad pointer into ImGuiIO) crash the process.