|
| 1 | +# Patterns |
| 2 | + |
| 3 | +Common patterns for working in this codebase. Applies to **`imgui-binding`**, **`imgui-lwjgl3`**, **`imgui-app`** and |
| 4 | +their tests/examples. The codegen plumbing under `buildSrc/` is **not** part of these patterns — see |
| 5 | +`.claude/rules/guardrails.md` for the rationale. |
| 6 | + |
| 7 | +## Working with the binding |
| 8 | + |
| 9 | +### The codegen loop |
| 10 | + |
| 11 | +The Java public API is materialized in two trees: |
| 12 | + |
| 13 | +``` |
| 14 | +imgui-binding/src/main/java/ ← hand-written, source of truth |
| 15 | +imgui-binding/src/generated/java/ ← codegen output, committed |
| 16 | +``` |
| 17 | + |
| 18 | +You edit the first; the generator (`buildSrc/`) writes the second. Both are checked in so downstream consumers see a |
| 19 | +normal Java project, but only the source tree is meaningful for review. |
| 20 | + |
| 21 | +``` |
| 22 | +# typical edit flow |
| 23 | +edit imgui-binding/src/main/java/imgui/Foo.java |
| 24 | +./gradlew :imgui-binding:generateApi |
| 25 | +./gradlew :imgui-binding:javadoc # catches doclint regressions early |
| 26 | +git add -p imgui-binding/src/main/java imgui-binding/src/generated/java |
| 27 | +git commit |
| 28 | +``` |
| 29 | + |
| 30 | +If you skip `generateApi`, the generated tree drifts and CI fails. If you commit only the generated tree, your change |
| 31 | +vanishes on the next regen. |
| 32 | + |
| 33 | +### Annotated stubs as a vocabulary |
| 34 | + |
| 35 | +The hand-written sources use annotations from `imgui.binding.annotation` to describe the wanted Java API on top of the |
| 36 | +C++ shape: |
| 37 | + |
| 38 | +| Annotation | Use | |
| 39 | +|--------------------------------|-------------------------------------------------------------------------------------------------------------------------| |
| 40 | +| `@BindingSource` | Class-level: this type participates in codegen. | |
| 41 | +| `@BindingMethod` | Method-level: emit a JNI bridge for this method. `name=` overrides the Java name; `callName=` overrides the C++ symbol. | |
| 42 | +| `@BindingField` | Field-level: emit getter/setter for a struct field. | |
| 43 | +| `@BindingAstEnum` | Bind a class to a clang-AST enum dump in `buildSrc/.../api/ast/`. | |
| 44 | +| `@OptArg` | Mark a parameter as having an overload that omits it. | |
| 45 | +| `@ArgValue`, `@ArgVariant` | Argument variant hints (e.g. `ImVec2` vs `(float, float)`). | |
| 46 | +| `@ReturnValue` | Return-value hints (`isStatic` for shared instances, etc.). | |
| 47 | +| `@TypeArray`, `@TypeStdString` | Map between Java arrays/`String` and C++ container types. | |
| 48 | +| `@ExcludedSource` | Skip this source when running codegen. | |
| 49 | + |
| 50 | +When adding a new method, copy the annotation pattern from a structurally similar nearby method — the generator is |
| 51 | +sensitive to placement and combinations. |
| 52 | + |
| 53 | +### Wrapping a native struct |
| 54 | + |
| 55 | +The recurring shape (see `ImVec2`, `ImInt`, `ImFontGlyph`, `ImColor`): |
| 56 | + |
| 57 | +1. Extend `imgui.binding.ImGuiStruct` if the type wraps a native pointer; otherwise it's a value type with public |
| 58 | + mutable fields or a backing `T[] data` array. |
| 59 | +2. Constructors: no-arg, copy (`new Foo(other)`), and per-field value form. |
| 60 | +3. Fluent `set(...)` returning `this`. |
| 61 | +4. `@Override` `toString` / `equals` / `hashCode`. Add `clone` with `@SuppressWarnings("MethodDoesntCallSuperMethod")` |
| 62 | + and the copy-constructor inside. |
| 63 | +5. Annotate `@BindingSource` if it's bound from upstream; otherwise leave plain. |
| 64 | + |
| 65 | +Don't invent a new layout — readers expect this one. |
| 66 | + |
| 67 | +### Out-parameters via `Im*` wrappers |
| 68 | + |
| 69 | +Dear ImGui's C++ API frequently takes `T*` for values the function will mutate (`bool* p_open`, `int* current_item`, |
| 70 | +`float* values`). Java can't do that with primitives, so the binding uses single-element wrappers under `imgui.type`: |
| 71 | + |
| 72 | +| C++ | Java wrapper | |
| 73 | +|-------------------------|--------------| |
| 74 | +| `bool*` | `ImBoolean` | |
| 75 | +| `int*` | `ImInt` | |
| 76 | +| `short*` | `ImShort` | |
| 77 | +| `long long*` | `ImLong` | |
| 78 | +| `float*` | `ImFloat` | |
| 79 | +| `double*` | `ImDouble` | |
| 80 | +| `char*`, `std::string*` | `ImString` | |
| 81 | + |
| 82 | +Pattern from a caller's perspective: |
| 83 | + |
| 84 | +```java |
| 85 | +ImBoolean isOpen = new ImBoolean(true); |
| 86 | +if (ImGui.begin("Window", isOpen)) { |
| 87 | + // ... |
| 88 | + ImGui.end(); |
| 89 | +} |
| 90 | +if (!isOpen.get()) { |
| 91 | + // window was closed via the title-bar X |
| 92 | +} |
| 93 | +``` |
| 94 | + |
| 95 | +When designing a new binding method that takes an out-parameter, mirror this — accept the wrapper, not a `boolean[]` or |
| 96 | +`AtomicReference`. |
| 97 | + |
| 98 | +### `@OptArg` and overload generation |
| 99 | + |
| 100 | +A C++ default argument becomes a series of Java overloads, generated automatically. In source you write the most general |
| 101 | +signature once and tag the optional tail: |
| 102 | + |
| 103 | +```java |
| 104 | +@BindingMethod |
| 105 | +public static native void ShowDemoWindow(@OptArg ImBoolean pOpen); |
| 106 | +``` |
| 107 | + |
| 108 | +The generator emits both `showDemoWindow()` and `showDemoWindow(ImBoolean)`. You don't need to hand-write the overloads, |
| 109 | +and you shouldn't. |
| 110 | + |
| 111 | +### Enums as constants on a holder class |
| 112 | + |
| 113 | +Dear ImGui's enums (`ImGuiWindowFlags`, `ImGuiCond`, `ImGuiKey`, …) are surfaced as classes under `imgui.flag.*` with |
| 114 | +`public static final int` constants and a private constructor. They're driven from clang AST dumps via |
| 115 | +`@BindingAstEnum`. When upstream adds or renames an enum value, regenerate the AST, then `generateApi` propagates it to |
| 116 | +the flag class. |
| 117 | + |
| 118 | +Don't hand-edit the flag classes. If a value is missing, the AST is stale. |
| 119 | + |
| 120 | +## Module boundaries |
| 121 | + |
| 122 | +``` |
| 123 | +imgui-binding ← Dear ImGui Java API + JNI. Zero runtime deps. |
| 124 | +imgui-lwjgl3 ← GLFW + OpenGL backend. Depends on lwjgl + imgui-binding. |
| 125 | +imgui-app ← Application abstraction. Depends on lwjgl + imgui-binding + imgui-lwjgl3, ships natives in the jar. |
| 126 | +example ← Demos. Depends on imgui-app. |
| 127 | +``` |
| 128 | + |
| 129 | +Rules of thumb: |
| 130 | + |
| 131 | +- New ImGui surface (a new function from upstream) → `imgui-binding`. |
| 132 | +- New backend wiring (alternate GL version, headless renderer) → new module sibling to `imgui-lwjgl3`. Don't add Vulkan |
| 133 | + to `imgui-lwjgl3`. |
| 134 | +- New Application convenience (an extra lifecycle hook) → `imgui-app`. Keep it minimal — it's meant as a starting point, |
| 135 | + not a framework. |
| 136 | +- Demo of a new feature → `example/src/main/java/Example*.java`, named `Example<Feature>.java`. |
| 137 | + |
| 138 | +Cross-module dependencies are one-way. `imgui-binding` must not depend on lwjgl or anything else. |
| 139 | + |
| 140 | +## Lifecycle of an `imgui-app` application |
| 141 | + |
| 142 | +`Application` extends `Window`. The `launch(...)` entry point runs: |
| 143 | + |
| 144 | +``` |
| 145 | +configure(Configuration) ← override to set title/size/etc. |
| 146 | +initWindow(Configuration) |
| 147 | +initImGui(Configuration) ← override to load fonts, set styles |
| 148 | +preRun() ← override: one-shot setup |
| 149 | +loop: |
| 150 | + preProcess() ← override: per-frame pre-work |
| 151 | + process() ← override: your UI |
| 152 | + postProcess() ← override: per-frame post-work |
| 153 | +postRun() ← override: one-shot teardown |
| 154 | +disposeImGui() |
| 155 | +disposeWindow() |
| 156 | +``` |
| 157 | + |
| 158 | +Threading: ImGui is single-threaded by design. Heavy work goes outside `process()` (background threads + lock-free |
| 159 | +hand-off), not inside it. The `imgui.app.Application` Javadoc spells this out. |
| 160 | + |
| 161 | +## Tests |
| 162 | + |
| 163 | +Tests use **JUnit Jupiter (5.x)** — see `imgui-binding/build.gradle`. They run via `./gradlew :imgui-binding:test`. |
| 164 | +There is no separate "integration" suite; test what you can in pure JVM, and rely on the example app for visual smoke |
| 165 | +tests. UI behavior changes (new widget rendering, font glyph coverage, viewport handling) cannot be unit-tested — run |
| 166 | +`./gradlew :example:run -PlibPath=...` and verify by eye. |
| 167 | + |
| 168 | +The `TypeName` Checkstyle rule is suppressed under `src/test/` so tests can use names like `ImGui_Test` without |
| 169 | +complaint, but otherwise tests follow the same code style as production code. |
| 170 | + |
| 171 | +## Building native libs locally |
| 172 | + |
| 173 | +Two flows, both documented in `README.md`: |
| 174 | + |
| 175 | +1. **Recommended**: `buildSrc/scripts/build.sh <windows|linux|macos>` — runs `vendor_freetype.sh`, calls `generateLibs`, |
| 176 | + drops the binary in `/tmp/imgui/dst/`. Matches what CI does. |
| 177 | +2. **Direct**: `./gradlew imgui-binding:generateLibs -Denvs=<csv> -Dlocal` — skip when you don't need FreeType or want |
| 178 | + output under the project tree. |
| 179 | + |
| 180 | +After building, point the example at the fresh binary: |
| 181 | + |
| 182 | +``` |
| 183 | +cp /tmp/imgui/dst/libimgui-java64.<so|dylib|dll> bin/ |
| 184 | +./gradlew :example:run -PlibPath=$PWD/bin |
| 185 | +``` |
| 186 | + |
| 187 | +Iterate `:example:run` (instead of `:imgui-binding:generateLibs` again) for Java-only changes — the native lib only |
| 188 | +needs rebuilding when JNI signatures or vendored sources change. |
| 189 | + |
| 190 | +## Submodule bumps in one paragraph |
| 191 | + |
| 192 | +`AGENTS.md` is the canonical guide. The condensed flow: read upstream's changelog and header diff before touching |
| 193 | +anything; bump the submodule pointer; `./gradlew generateAst` and revert any AST changes outside your scope; update |
| 194 | +annotated sources to match upstream's renames/removals; `./gradlew :imgui-binding:generateApi`; run javadoc locally and |
| 195 | +fix doclint hits; rebuild natives. Submodule bumps go in their own commit, separate from new-feature exposure. |
| 196 | + |
| 197 | +## Where to look first |
| 198 | + |
| 199 | +- `imgui.ImGui` — the giant entry-point class (1.4 MB worth of generated bindings + the static lib loader). |
| 200 | +- `imgui.binding.ImGuiStruct` — the JNI handshake. |
| 201 | +- `imgui.app.Application` — the lifecycle template you'd subclass. |
| 202 | +- `imgui-lwjgl3/.../ImGuiImplGl3.java` — reference for what a full backend looks like. |
| 203 | +- `example/src/main/java/Main.java` — the smallest end-to-end runnable, plus per-extension `Example*.java` siblings. |
| 204 | +- `AGENTS.md` (repo root) — submodule-bump procedure, generator gotchas, doclint pitfalls. |
0 commit comments