Direct vs. forked libraries¶
ImGui Bundle includes ~24 external C++ libraries. Each lives under external/<LibName>/ and is typically a git submodule.
Some libraries can be used as-is from their upstream repository. Others need modifications to work with Python bindings — for example, replacing pointer + count C APIs with std::vector, or swapping C function pointers for std::function. In those cases, a fork is maintained on GitHub under github.com/pthom.
Run just libs_info to see the full picture:
NAME FORK OFFICIAL PATH
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------
imgui https://github.com/pthom/imgui.git https://github.com/ocornut/imgui.git external/imgui/imgui
imgui_test_engine https://github.com/pthom/imgui_test_engine.git https://github.com/ocornut/imgui_test_engine.git external/imgui_test_engine/imgui_test_engine
glfw https://github.com/glfw/glfw.git external/glfw/glfw
hello_imgui https://github.com/pthom/hello_imgui.git external/hello_imgui/hello_imgui
im_cool_bar https://github.com/pthom/ImCoolBar.git https://github.com/aiekick/ImCoolBar.git external/ImCoolBar/ImCoolBar
im_file_dialog https://github.com/pthom/ImFileDialog.git https://github.com/dfranx/ImFileDialog.git external/ImFileDialog/ImFileDialog
imgui_command_palette https://github.com/pthom/imgui-command-palette.git https://github.com/hnOsmium0001/imgui-command-palette.git external/imgui-command-palette/imgui-command-palette
imgui_knobs https://github.com/pthom/imgui-knobs.git https://github.com/altschuler/imgui-knobs external/imgui-knobs/imgui-knobs
imgui_node_editor https://github.com/pthom/imgui-node-editor.git https://github.com/thedmd/imgui-node-editor.git external/imgui-node-editor/imgui-node-editor
imgui_rich_md https://github.com/pthom/imgui_rich_md.git (renderer by mekhontsev/imgui_md, rewritten) external/imgui_rich_md/imgui_rich_md
imgui_microtex https://github.com/pthom/MicroTeX.git https://github.com/NanoMichael/MicroTeX.git external/imgui_microtex/MicroTeX
imgui_tex_inspect https://github.com/pthom/imgui_tex_inspect.git https://github.com/andyborrell/imgui_tex_inspect.git external/imgui_tex_inspect/imgui_tex_inspect
imgui_toggle https://github.com/pthom/imgui_toggle.git https://github.com/cmdwtf/imgui_toggle.git external/imgui_toggle/imgui_toggle
imgui_color_text_edit https://github.com/pthom/ImGuiColorTextEdit.git https://github.com/goossens/ImGuiColorTextEdit.git external/ImGuiColorTextEdit/ImGuiColorTextEdit
imguizmo https://github.com/pthom/ImGuizmo.git https://github.com/CedricGuillemet/ImGuizmo.git external/ImGuizmo/ImGuizmo
immvision https://github.com/pthom/immvision.git external/immvision/immvision
implot https://github.com/pthom/implot.git https://github.com/epezent/implot.git external/implot/implot
implot3d https://github.com/pthom/implot3d.git https://github.com/brenocq/implot3d.git external/implot3d/implot3d
nanovg https://github.com/pthom/nanovg.git https://github.com/memononen/nanovg.git external/nanovg/nanovg
im_anim https://github.com/pthom/ImAnim.git https://github.com/soufianekhiat/ImAnim.git external/ImAnim/ImAnimLibraries with a FORK column are maintained as forks with bundle-specific patches.
Libraries with only an OFFICIAL column are used directly from upstream.
A few internal libraries (e.g.
immapp,imgui_explorer) have no git URL — they live directly in the repo.
When is a fork created?¶
A fork is created when a library needs C++ source modifications that cannot be achieved through the binding generator alone. Typical reasons:
Python API adaptations that cannot be done outside the library (see the next section): for example C function pointers replaced by
std::function.Bug fixes or behavior changes specific to ImGui Bundle’s usage.
Build adaptations for cross-platform support.
If a library can be bound without source modifications, it is used directly from upstream (no fork needed).
Adapting an API for Python: where does the change go?¶
A patch in a fork is the most expensive place for a Python adaptation: it must survive every rebase on upstream, and it changes the C++ library for everybody. Try the following mechanisms in this order, and stop at the first one that does the job:
| # | Mechanism | Where it lives | Use it when |
|---|---|---|---|
| 1 | litgen option | external/<lib>/bindings/ (generator script or options file) | Excluding, renaming, turning output parameters into return values, buffers into numpy arrays, etc. |
| 2 | Wrapper header (“pywrappers”) | e.g. external/imgui/imgui_pywrappers/ | The Python version is plain C++ that litgen can bind: a new struct, std::string, std::optional, a tuple. |
| 3 | Custom binding | options.custom_bindings.add_custom_bindings_to_main_module / _to_class / _to_submodule | The binding needs nanobind types (nb::ndarray, nb::object), access to members from outside the class, or control over a whole set of overloads. |
| 4 | Fork patch | the library itself | The behavior of the library must change (bug fix, feature), or a type must differ in the Python build. |
Why this order:
A wrapper header is processed by litgen like any other header: bindings, stubs and docstrings are generated, and the C++ is real code (compiled on its own, visible to the IDE). It is built only when Python bindings are enabled.
A custom binding can do what a header cannot, but its C++ lives inside a Python string (errors show up only when the module compiles) and its stub is written by hand. Keep them for the cases listed above.
When a custom binding replaces overloaded functions, it must own the whole overload set: exclude all the C++ overloads and register them all in the custom binding. Python overloads must be consecutive in the stubs, and the registration order decides which overload wins.
Examples in the Dear ImGui bindings (external/imgui/bindings/litgen_options_imgui.py):
| Mechanism | Example |
|---|---|
| litgen option | fn_exclude_by_name_and_signature hides the pointer + count overloads of ImDrawList |
| Wrapper header | DockBuilderSplitNode() returning a struct instead of two output parameters; drag and drop payloads carrying a Python id |
| Custom binding | ImTextureData.get_pixels_array() (numpy view), imgui.set_window_focus() (owns its overload set), the clipboard callbacks of ImGuiPlatformIO (static trampolines) |
| Fork patch | ImGuiInputTextCallback is a std::function in the Python build |
The fork branch model¶
Branches and remotes¶
Each forked library has two git remotes:
| Remote | Points to | Branch tracked |
|---|---|---|
official | Upstream author’s repo (e.g. ocornut/imgui) | The upstream branch (e.g. docking, master, main) |
fork | pthom’s fork (e.g. pthom/imgui) | imgui_bundle |
The fork branch is always named imgui_bundle. All bundle-specific changes live on this branch.
Non-forked libraries have a single official remote pointing to their upstream repo.
Rebase, not merge¶
When updating a fork to incorporate new upstream changes, we rebase the imgui_bundle branch on top of the latest upstream — we do not merge. This keeps the bundle-specific diff small, self-contained, and easy to review. It also makes future rebases easier since there are no merge commits to carry forward.
The typical update sequence is:
Tag the current fork state (safety snapshot):
just libs_tag <name>Rebase the fork branch on upstream:
just libs_rebase <name>Force-push the rebased branch to the fork remote (if the rebase changed anything)
See Update existing bindings for the full step-by-step workflow.
Marking bundle-specific changes¶
In code: [ADAPT_IMGUI_BUNDLE] markers¶
All bundle-specific modifications in forked library source code are bracketed with comment markers:
// [ADAPT_IMGUI_BUNDLE]
// ... bundle-specific code ...
// [/ADAPT_IMGUI_BUNDLE]This makes it easy to find all bundle adaptations by searching for ADAPT_IMGUI_BUNDLE, and to review the diff against upstream.
Example from imgui.h: a function added for the ImGui Explorer:
//[ADAPT_IMGUI_BUNDLE]
IMGUI_API void ShowDemoWindow_MaybeDocked(
bool create_window, bool* p_open = NULL,
ImGuiWindowFlags initial_extra_flags = 0,
ImVec2 window_pos = ImVec2(0, 0), ImVec2 window_size = ImVec2(0, 0));
//[/ADAPT_IMGUI_BUNDLE]In commits: [Bundle] prefix¶
Bundle-specific commits use a [Bundle] prefix in the commit message. This clearly distinguishes them from upstream commits when reading the git log. Commits that are proposed upstream as they are (see “Contributing fixes upstream” below) do not carry the prefix.
Some older commits may use [ADAPT_IMGUI_BUNDLE] as the prefix: this is equivalent.
The Dear ImGui fork: one commit per theme¶
The Dear ImGui fork is kept as a short list of thematic commits on top of upstream’s docking branch, so that each of them can be reviewed, rebased or dropped on its own:
$ git log --oneline official/docking..imgui_bundle
[Bundle] imgui_demo.cpp: ShowDemoWindow_MenuSectionWindow(), the window of a section of the demo's menus
[Bundle] Move the docking internal types from imgui.cpp to imgui_internal.h
[Bundle] imgui_freetype: PlutoSVG's FreeType hooks are in <plutosvg-ft.h>
[Bundle] Python build: throwing IM_ASSERT support and std::function callback typedefs
[Bundle] imgui_demo.cpp adaptations for the ImGui Explorer
[Bundle] Emscripten / Pyodide: SDL2 backend fixes
[Bundle] ImGuiContext::InputTextMultilineOverride
[Bundle] ImGuiContext::BeginWindowCallback / EndWindowCallback, called by Begin() and End()
Widgets: Sliders & Drags display the value in a tooltip on touchscreens.
Stack Layout implementation v2.2
Internals: CallContextHooks(): fixed iterating a reallocated array when a hook callback adds a hook.
Tables: fixed items in a cell being laid out on one line when the table is submitted in a horizontal layout (e.g. a menu bar).StackLayout (
BeginHorizontal/BeginVertical/Spring) comes from thedmd’s branch: its three commits, cherry-picked as they are. The first two are fixes he proposes to Dear ImGui (the Tables one is #9589); the third holds the layout itself. A fix of ours goes on top of them, as a commit of its own, proposed upstream to him. When he publishes a new version: replace his commits, and drop the fixes he integrated.When a change belongs to an existing theme, fold it into that commit (
git commit --fixupthengit rebase --autosquash) instead of stacking a new commit on top. A new commit is for a new theme.The two commits used by imgui-node-editor are generic (two callbacks called by
Begin()/End(), and an override point forInputTextMultiline()): Dear ImGui does not know about the node editor. Each one defines a macro (IMGUI_HAS_BEGIN_END_WINDOW_CALLBACKS,IMGUI_HAS_INPUT_TEXT_MULTILINE_OVERRIDE) that the node editor tests. They are also published as patch files in the node editor fork (see below).What remains specific to Python in this fork is small: the two
std::functioncallback typedefs (ImGuiInputTextCallback,ImGuiSizeCallback), destructors markednoexcept(false)becauseIM_ASSERTthrows in the Python build, and theIMGUI_BUNDLE_PYTHON_UNSUPPORTED_APImarker. All other Python adaptations live in the bundle (mechanisms 1 to 3 above).
The imgui-node-editor fork¶
The fork of imgui-node-editor is organised in the same way, on top of upstream’s master branch: generic commits first (fixes, the canvas, widgets inside nodes, node width, angled links), then the tests and the documentation. Its first commits, each with tests in upstream’s headless suite, are also on branches of their own, for upstream. What it adds is described in its own docs/fork_imgui_bundle.md.
It builds against a stock Dear ImGui. The code that needs one of the two Dear ImGui commits above is guarded by their macros.
It carries no Python adaptation and no Bundle-specific commit: its public API is upstream’s, plus additions. The functions that fill an array are wrapped in
external/imgui-node-editor/imgui_node_editor_pywrappers/(mechanism 2).Style.color_()andConfig.settings_fileare custom bindings (mechanism 3):SettingsFileis aconst char*that the editor keeps, so the Python property interns the strings it receives.It has automated tests (
tests/node_editor_tests.cpp, imgui_test_engine). The bundle runs them in CI with.github/ci_automation_tests/ci_node_editor_tests.cpp(--autofor the non-interactive mode).After rebasing the Dear ImGui fork, refresh
misc/imgui_patches/in the node editor fork (the command is in itsdocs/fork_imgui_bundle.md, chapter 3) and run the tests: they are what tells that popups, combos and multiline text still work inside a node with the new Dear ImGui.
Preprocessor macros¶
Two preprocessor macros control conditional compilation in forked code:
IMGUI_BUNDLE_PYTHON_API: Code inside#ifdef IMGUI_BUNDLE_PYTHON_APIis compiled only when building Python bindings. Use this to provide Python-friendly alternatives.IMGUI_BUNDLE_PYTHON_UNSUPPORTED_API: Code inside this guard is excluded from Python bindings (it is always defined, but the binding generator skips it). Use this to hide C++ APIs that cannot be wrapped.
These are often used together to swap an unwrappable API for a Python-friendly one (try the mechanisms 1 to 3 above first: a function that fills an array, for example, is better served by a wrapper header that returns a std::vector):
// From imgui.h — replacing function pointer with std::function
#ifdef IMGUI_BUNDLE_PYTHON_API
using ImGuiInputTextCallback = std::function<int(ImGuiInputTextCallbackData*)>;
#else
#ifdef IMGUI_BUNDLE_PYTHON_UNSUPPORTED_API
typedef int (*ImGuiInputTextCallback)(ImGuiInputTextCallbackData* data);
#endif
#endifSee Adding a new library (Step 3) for more adaptation patterns.
Contributing fixes upstream¶
If a fix or improvement is not specific to ImGui Bundle (e.g. a genuine bug fix, a useful feature), it should be contributed upstream:
Do not use
[Bundle]prefix or[ADAPT_IMGUI_BUNDLE]markers.Do not use
IMGUI_BUNDLE_PYTHON_API/IMGUI_BUNDLE_PYTHON_UNSUPPORTED_APIguards.Submit a PR to the upstream repository.
Once merged upstream, the fix will be picked up during the next rebase.
Tooling¶
Python module: bundle_libs_tooling¶
The library management logic lives in external/bindings_generation/bundle_libs_tooling/:
| File | Purpose |
|---|---|
all_external_libraries.py | Central registry — defines all ~24 libraries as ExternalLibrary instances in the ALL_LIBS list. Contains functions for bulk operations (reattach, fetch, pull, check upstream). |
external_library.py | ExternalLibrary dataclass — holds name, git URLs, branches, remote names, and provides methods for git operations (add remotes, fetch, rebase, tag, etc.). |
shell_commands.py | ShellCommands helper — runs multiline shell commands with echo and error handling. |
The ExternalLibrary dataclass fields:
@dataclass
class ExternalLibrary:
name: str # Library name (e.g. "ImCoolBar")
official_git_url: Optional[str] # Upstream repo URL
official_branch: str = "master" # Upstream branch to track
fork_git_url: Optional[str] # Fork repo URL (None if not forked)
fork_branch: str = "imgui_bundle" # Fork branch (always "imgui_bundle")
is_published_in_python: bool # Whether this lib gets Python bindings
is_sub_library: bool # Sub-library (e.g. MicroTeX inside imgui_microtex)Justfile recipes: just libs_*¶
The justfile at the repo root wraps the Python tooling into convenient commands:
| Command | Description |
|---|---|
just libs_info | Show all libraries with their remotes and paths |
just libs_reattach | Reattach all submodules to their correct branches and remotes |
just libs_fetch | Fetch all remotes for all submodules |
just libs_pull | Pull all submodules |
just libs_check_upstream | Check which forked libraries have new upstream changes |
just libs_log <name> | Show new upstream commits not yet in a fork |
just libs_tag <name> | Push a dated tag (bundle_YYYYMMDD) to a fork |
just libs_check_pushed | Check that every submodule commit the bundle points to is on a remote branch (before pushing the bundle) |
just bundle_push | Push the bundle, once libs_check_pushed passes |
just libs_rebase <name> | Tag current state, then rebase fork branch on upstream |
just libs_bindings <name> | Regenerate Python bindings for one library |
just libs_bindings_all | Regenerate all Python bindings |
Library names can be passed in snake_case or original form (e.g. just libs_log imgui or just libs_log ImGuiColorTextEdit).
Common operations¶
After a fresh clone: reattach submodules¶
By default, git submodules are in “detached HEAD” mode. To set up the correct remotes (official / fork) and attach each submodule to its tracking branch:
just libs_reattachThis is typically the first thing to do after cloning the repository when you plan to work on library updates.
Check what’s new upstream¶
just libs_check_upstreamExample output:
Unchanged libraries: glfw, hello_imgui, ImCoolBar, ImFileDialog, imgui-command-palette, ...
Libraries with new changes in official repo: imgui, imgui_test_engine, ImGuiColorTextEditThen inspect the new commits for a specific library:
just libs_log imguiTag a fork (safety snapshot)¶
Before rebasing, create a dated tag so you can recover the previous state:
just libs_tag imguiThis creates a tag named bundle_YYYYMMDD and pushes it to the fork remote.
Rebase a fork on upstream¶
just libs_rebase imguiThis command:
Creates a dated tag (safety snapshot)
Fetches both
officialandforkremotesChecks out the
imgui_bundlebranchRebases it on top of
official/<upstream_branch>
If the rebase introduces changes, you need to force-push manually:
cd external/imgui/imgui
git push fork --force-with-lease
cd -Update a non-forked library¶
For libraries without a fork, simply pull:
cd external/hello_imgui/hello_imgui
git pull
cd -A library pinned to a release tag (pinned_tag in all_external_libraries.py: glfw, the GLFW of the desktop Python wheel) has no tracking branch, and just libs_pull skips it. Its update is a deliberate step: the new tag in all_external_libraries.py, then git fetch official --tags && git checkout <tag> in the submodule.
Or pull all submodules at once:
just libs_pull